diff --git a/BraveShared/BraveStrings.swift b/BraveShared/BraveStrings.swift index 44b398d5089..4a416c2f31f 100644 --- a/BraveShared/BraveStrings.swift +++ b/BraveShared/BraveStrings.swift @@ -521,6 +521,14 @@ extension Strings { public static let ShareWithMenuItem = NSLocalizedString("ShareWithMenuItem", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Share with...", comment: "Title for sharing url menu item") public static let DownloadsMenuItem = NSLocalizedString("DownloadsMenuItem", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Downloads", comment: "Title for downloads menu item") public static let DownloadsPanelEmptyStateTitle = NSLocalizedString("DownloadsPanelEmptyStateTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Downloaded files will show up here.", comment: "Title for when a user has nothing downloaded onto their device, and the list is empty.") + + // MARK: - Themes + + public static let ThemesDisplayBrightness = NSLocalizedString("ThemesDisplayBrightness", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Display & Brightness", comment: "Setting to choose the user interface theme for normal browsing mode, contains choices like 'light' or 'dark' themes") + public static let ThemesDisplayBrightnessFooter = NSLocalizedString("ThemesDisplayBrightnessFooter", tableName: "BraveShared", bundle: Bundle.braveShared, value: "These settings are not applied in private browsing mode.", comment: "Text specifying that the above setting does not impact the user interface while they user is in private browsing mode.") + public static let ThemesAutomaticOption = NSLocalizedString("ThemesAutomaticOption", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Automatic", comment: "Selection to automatically color/style the user interface.") + public static let ThemesLightOption = NSLocalizedString("ThemesLightOption", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Light", comment: "Selection to color/style the user interface with a light theme.") + public static let ThemesDarkOption = NSLocalizedString("ThemesDarkOption", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Dark", comment: "Selection to color/style the user interface with a dark theme") } // MARK: - Quick Actions diff --git a/Client.xcodeproj/project.pbxproj b/Client.xcodeproj/project.pbxproj index bbd5a680377..c78ea3c1be5 100644 --- a/Client.xcodeproj/project.pbxproj +++ b/Client.xcodeproj/project.pbxproj @@ -492,6 +492,7 @@ 5D1DC57220AE005400905E5A /* JSInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1DC56820AE005400905E5A /* JSInjector.swift */; }; 5D1DC57320AE005400905E5A /* SyncSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1DC56920AE005400905E5A /* SyncSite.swift */; }; 5D1DC57420AE005400905E5A /* ios-sync.js in Resources */ = {isa = PBXBuildFile; fileRef = 5D1DC56A20AE005400905E5A /* ios-sync.js */; }; + 5D21AE082303B70E00B30D97 /* Themes in Resources */ = {isa = PBXBuildFile; fileRef = 5D21AE002303B70E00B30D97 /* Themes */; }; 5D24AF8221BA459000F9506A /* BlocklistName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D24AF8121BA459000F9506A /* BlocklistName.swift */; }; 5D24AF8421BA489A00F9506A /* ContentBlocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D24AF8321BA489A00F9506A /* ContentBlocker.swift */; }; 5D24AF9021BA4D7700F9506A /* ContentBlockerRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D24AF8F21BA4D7700F9506A /* ContentBlockerRegion.swift */; }; @@ -499,6 +500,7 @@ 5D6DDEFE2141B6A1001FF0AE /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6DDEF62141B6A0001FF0AE /* Preferences.swift */; }; 5D6DDF0021428CF0001FF0AE /* DAUTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6DDEFF21428CF0001FF0AE /* DAUTests.swift */; }; 5D7001C222249F7B00E576FB /* ShortNameToFileMapping.json in Resources */ = {isa = PBXBuildFile; fileRef = 5D7001BA22249F7A00E576FB /* ShortNameToFileMapping.json */; }; + 5D8AE6AF230C76B60096C845 /* AppearanceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8AE6AE230C76B60096C845 /* AppearanceExtensions.swift */; }; 5DE5250D218CE92C000DFAA6 /* block-ads.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DE52508218CE92B000DFAA6 /* block-ads.json */; }; 5DE5250E218CE92C000DFAA6 /* block-images.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DE52509218CE92B000DFAA6 /* block-images.json */; }; 5DE5250F218CE92C000DFAA6 /* upgrade-http.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DE5250A218CE92B000DFAA6 /* upgrade-http.json */; }; @@ -1863,6 +1865,7 @@ 5D1DC56820AE005400905E5A /* JSInjector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSInjector.swift; sourceTree = ""; }; 5D1DC56920AE005400905E5A /* SyncSite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncSite.swift; sourceTree = ""; }; 5D1DC56A20AE005400905E5A /* ios-sync.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "ios-sync.js"; sourceTree = ""; }; + 5D21AE002303B70E00B30D97 /* Themes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Themes; sourceTree = ""; }; 5D24AF8121BA459000F9506A /* BlocklistName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocklistName.swift; sourceTree = ""; }; 5D24AF8321BA489A00F9506A /* ContentBlocker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlocker.swift; sourceTree = ""; }; 5D24AF8F21BA4D7700F9506A /* ContentBlockerRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRegion.swift; sourceTree = ""; }; @@ -1870,6 +1873,7 @@ 5D6DDEF62141B6A0001FF0AE /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 5D6DDEFF21428CF0001FF0AE /* DAUTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAUTests.swift; sourceTree = ""; }; 5D7001BA22249F7A00E576FB /* ShortNameToFileMapping.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ShortNameToFileMapping.json; sourceTree = ""; }; + 5D8AE6AE230C76B60096C845 /* AppearanceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppearanceExtensions.swift; path = Extensions/AppearanceExtensions.swift; sourceTree = ""; }; 5DE52508218CE92B000DFAA6 /* block-ads.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "block-ads.json"; sourceTree = ""; }; 5DE52509218CE92B000DFAA6 /* block-images.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "block-images.json"; sourceTree = ""; }; 5DE5250A218CE92B000DFAA6 /* upgrade-http.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "upgrade-http.json"; sourceTree = ""; }; @@ -2769,6 +2773,7 @@ 595E0EE021CAEF5B00813D49 /* FileManagerExtension.swift */, 592F521D2217327B0078395E /* HttpCookieExtension.swift */, F930CDAD2270015C00A23FE1 /* U2FExtensions.swift */, + 5D8AE6AE230C76B60096C845 /* AppearanceExtensions.swift */, ); indentWidth = 4; name = Extensions; @@ -3398,6 +3403,14 @@ path = sync; sourceTree = ""; }; + 5D26E89C22E117660015D7E9 /* Themes */ = { + isa = PBXGroup; + children = ( + 7B3631E91C244FEE00D12AF9 /* Theme.swift */, + ); + path = Themes; + sourceTree = ""; + }; 5D6DDEDF214003A6001FF0AE /* Analytics */ = { isa = PBXGroup; children = ( @@ -3708,7 +3721,7 @@ E65607601C08B4E200534B02 /* SearchInputView.swift */, E63ED7D71BFCD9990097D08E /* LoginTableViewCell.swift */, C4E3984B1D21F2FD004E89BA /* TabTrayButtonExtensions.swift */, - 7B3631E91C244FEE00D12AF9 /* Theme.swift */, + 5D26E89C22E117660015D7E9 /* Themes */, 59A68B1F857A8638598A63A0 /* TwoLineCell.swift */, D863C8E31F68BFC20058D95F /* GradientProgressBar.swift */, 27D87C2D2152B50200FB55C6 /* GradientView.swift */, @@ -4259,6 +4272,7 @@ D03F8F22200EAC1E003C2224 /* AllFramesAtDocumentStart.js */, D0FCF8041FE4772D004A7995 /* MainFrameAtDocumentEnd.js */, D0FCF8051FE4772D004A7995 /* MainFrameAtDocumentStart.js */, + 5D21AE002303B70E00B30D97 /* Themes */, 3BC659581E5BA505006D560F /* top_sites.json */, 3BC659481E5BA4AE006D560F /* TopSites */, C6345ECA2113B3A000CFB983 /* SearchPlugins */, @@ -5225,6 +5239,7 @@ 4422D4AD21BFFB7600BF1855 /* LICENSE in Resources */, 5EB57D0222FDC0CB00A07325 /* FullscreenHelper.js in Resources */, E4B7B7641A793CF20022C5E0 /* CharisSILR.ttf in Resources */, + 5D21AE082303B70E00B30D97 /* Themes in Resources */, 4422D56D21BFFB7F00BF1855 /* make_unicode_groups.py in Resources */, E4B7B7681A793CF20022C5E0 /* FiraSans-Bold.ttf in Resources */, E4B7B7781A793CF20022C5E0 /* FiraSans-Italic.ttf in Resources */, @@ -5874,6 +5889,7 @@ A83E5AB71C1D993D0026D912 /* UIPasteboardExtensions.swift in Sources */, C6B81B8A212D84BD00996084 /* ImageCacheType.swift in Sources */, D8EFFA0C1FF5B1FA001D3A09 /* NavigationRouter.swift in Sources */, + 5D8AE6AF230C76B60096C845 /* AppearanceExtensions.swift in Sources */, D0E55C4F1FB4FD23006DC274 /* FormPostHelper.swift in Sources */, E650754E1E37F6AE006961AC /* GeometryExtensions.swift in Sources */, D3972BF41C22412B00035B87 /* TitleActivityItemProvider.swift in Sources */, diff --git a/Client/Application/AppDelegate.swift b/Client/Application/AppDelegate.swift index aebf86dc47c..a5d76c309de 100644 --- a/Client/Application/AppDelegate.swift +++ b/Client/Application/AppDelegate.swift @@ -209,6 +209,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati // Override point for customization after application launch. var shouldPerformAdditionalDelegateHandling = true + // BVC generally handles theme applying, but in some instances views are established + // before then (e.g. passcode, so can be privacy concern, meaning this should be called ASAP) + // In order to properly apply background and align this with the rest of the UI (keyboard / header) + // this needs to be called. UI could be handled internally to view systems, + // but then keyboard may misalign with Brave selected theme override + Theme.of(nil).applyAppearanceProperties() + UIScrollView.doBadSwizzleStuff() #if BUDDYBUILD @@ -266,13 +273,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati } AdblockResourceDownloader.shared.startLoading() - - UINavigationBar.appearance().tintColor = BraveUX.BraveOrange - - (UISwitch.appearance() as UISwitch).do { - $0.tintColor = BraveUX.SwitchTintColor - $0.onTintColor = BraveUX.BraveOrange - } return shouldPerformAdditionalDelegateHandling } diff --git a/Client/Application/ClientPreferences.swift b/Client/Application/ClientPreferences.swift index 72cfe40ca0b..fdc403db0d4 100644 --- a/Client/Application/ClientPreferences.swift +++ b/Client/Application/ClientPreferences.swift @@ -41,11 +41,14 @@ extension Preferences { static let blockPopups = Option(key: "general.block-popups", default: true) /// Controls how the tab bar should be shown (or not shown) static let tabBarVisibility = Option(key: "general.tab-bar-visiblity", default: TabBarVisibility.always.rawValue) + /// Defines the user's normal browsing theme + /// `system`, follows the current OS display mode + static let themeNormalMode = Option(key: "general.normal-mode-theme", default: Theme.DefaultTheme.system.rawValue) + static let themePrivateMode = Option(key: "general.private-mode-theme", default: Theme.DefaultTheme.private.rawValue) /// Specifies whether the bookmark button is present on toolbar static let showBookmarkToolbarShortcut = Option(key: "general.show-bookmark-toolbar-shortcut", default: UIDevice.isIpad) /// Sets Desktop UA for iPad by default (iOS 13+ & iPad only) static let alwaysRequestDesktopSite = Option(key: "general.always-request-desktop-site", default: UIDevice.isIpad) - /// Whether or not a user has enabled Night Mode. /// /// Currently unused diff --git a/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json b/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json new file mode 100644 index 00000000000..01245611cbe --- /dev/null +++ b/Client/Assets/Themes/ACE618A3-D6FC-45A4-94F2-1793C40AE927.json @@ -0,0 +1,39 @@ +{ + "uuid": "ACE618A3-D6FC-45A4-94F2-1793C40AE927", + "title": "Default light", + "url": "www.brave.com", + "description": "The standard default light theme", + "thumbnail": "https://www.google.com", + "isDark": false, + "enabled": true, + + "colors": { + "header": "0xf4f4f4", + "footer": "0xf8f8f8", + "home": "0xe8e8e8", + "addressBar": "0xffffff", + "border": "0x000000", + "accent": "0xfb542b", + "tints": { + "home": "0x434351", + "header": "0x434351", + "footer": "0x434351", + "addressBar": "0x434351" + }, + "transparencies": { + "addressBarAlpha": 1.0, + "borderAlpha": 0.2, + }, + "stats": { + "ads": "0xFA4214", + "trackers": "0x742BC4", + "httpse": "0x9339D4", + "timeSaved": "0x222326" + } + }, + "images": { + "header": "https://www.google.com", + "footer": "https://www.google.com", + "home": "https://www.google.com", + } +} diff --git a/Client/Assets/Themes/B900A41F-2C02-4664-9DE4-C170956339AC.json b/Client/Assets/Themes/B900A41F-2C02-4664-9DE4-C170956339AC.json new file mode 100644 index 00000000000..33bcf1caa60 --- /dev/null +++ b/Client/Assets/Themes/B900A41F-2C02-4664-9DE4-C170956339AC.json @@ -0,0 +1,40 @@ +{ + "uuid": "B900A41F-2C02-4664-9DE4-C170956339AC", + "title": "Default dark", + "url": "www.brave.com", + "description": "The standard default dark theme", + "thumbnail": "https://www.google.com", + "isDark": true, + "enabled": true, + + "colors": { + "header": "0x282828", + "footer": "0x282828", + "home": "0x1c1c1c", + "addressBar": "0x444444", + "border": "0xffffff", + "accent": "0xff631c", + "tints": { + "home": "0xE7E6E9", + "header": "0xE7E6E9", + "footer": "0xE7E6E9", + "addressBar": "0xE7E6E9" + }, + "transparencies": { + "addressBarAlpha": 1.0, + "borderAlpha": 0.2, + }, + "stats": { + "ads": "0xFA4214", + "trackers": "0x1bc760", + "httpse": "0x9339D4", + "timeSaved": "0xffffff" + } + }, + "images": { + "header": "https://www.google.com", + "footer": "https://www.google.com", + "home": "https://www.google.com", + } +} + diff --git a/Client/Assets/Themes/C5CB0D9A-5467-432C-AB35-1A78C55CFB41.json b/Client/Assets/Themes/C5CB0D9A-5467-432C-AB35-1A78C55CFB41.json new file mode 100644 index 00000000000..09c2bbeb7c3 --- /dev/null +++ b/Client/Assets/Themes/C5CB0D9A-5467-432C-AB35-1A78C55CFB41.json @@ -0,0 +1,39 @@ +{ + "uuid": "C5CB0D9A-5467-432C-AB35-1A78C55CFB41", + "title": "Default private", + "url": "www.brave.com", + "description": "The standard default private theme", + "thumbnail": "https://www.google.com", + "isDark": true, + "enabled": true, + + "colors": { + "header": "0x1B0C32", + "footer": "0x1B0C32", + "home": "0x301B5A", + "addressBar": "0x3D2742", + "border": "0xffffff", + "accent": "0xcf68ff", + "tints": { + "home": "0xE7E6E9", + "header": "0xE7E6E9", + "footer": "0xE7E6E9", + "addressBar": "0xE7E6E9" + }, + "transparencies": { + "addressBarAlpha": 1.0, + "borderAlpha": 0.2, + }, + "stats": { + "ads": "0xFA4214", + "trackers": "0x1bc760", + "httpse": "0x9339D4", + "timeSaved": "0xffffff" + } + }, + "images": { + "header": "https://www.google.com", + "footer": "https://www.google.com", + "home": "https://www.google.com", + } +} diff --git a/Client/Extensions/AppearanceExtensions.swift b/Client/Extensions/AppearanceExtensions.swift new file mode 100644 index 00000000000..241ba77fedc --- /dev/null +++ b/Client/Extensions/AppearanceExtensions.swift @@ -0,0 +1,122 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation + +extension Theme { + func applyAppearanceProperties() { + + // `appearance` modifications only impact UI items not current visible + + // important! for privacy concerns, otherwise UI can bleed through + UIView.appearance(whenContainedInInstancesOf: [BasePasscodeViewController.self]).appearanceBackgroundColor = colors.home + + UIToolbar.appearance().tintColor = colors.accent + UIToolbar.appearance().backgroundColor = colors.footer + + UINavigationBar.appearance().tintColor = colors.accent + UINavigationBar.appearance().appearanceBarTintColor = colors.header + + UISwitch.appearance().onTintColor = colors.accent + + // This is a subtle "abuse" of theme colors + // In order to properly style things, `addressBar` has been utilized to offer contrast to `home`/`header`, as many of the themes utilize similar colors. + // These used colors have been mapped, primarily for table usage, and to understand how table colors relate to each other. + // Any change to a single tableView property that currently uses one of these will probably have odd behavior and must be thoroughly tested + + /// Used as color a table will use as the base (e.g. background) + let tablePrimaryColor = colors.home + /// Used to augment `tablePrimaryColor` above + let tableSecondaryColor = colors.header + + // Will become the color for whatever in the table is .clear + // In some cases this is the header, footer, cell, or a combination of them. + // Be careful adjusting colors here, and make sure impact is well known + UITableView.appearance().appearanceBackgroundColor = tablePrimaryColor + UITableView.appearance().appearanceSeparatorColor = colors.border.withAlphaComponent(colors.transparencies.borderAlpha) + + UITableViewCell.appearance().tintColor = colors.accent + UITableViewCell.appearance().backgroundColor = tableSecondaryColor + + UIView.appearance(whenContainedInInstancesOf: [UITableViewHeaderFooterView.self]).appearanceBackgroundColor = tablePrimaryColor + + UILabel.appearance(whenContainedInInstancesOf: [UITableView.self]).appearanceTextColor = colors.tints.home + + AddEditHeaderView.appearance().appearanceBackgroundColor = tableSecondaryColor + UITextField.appearance().appearanceTextColor = colors.tints.home + UITextField.appearance().keyboardAppearance = isDark ? .dark : .light + + // Sync items + SyncViewController.SyncView.appearance(whenContainedInInstancesOf: [UINavigationController.self]).appearanceBackgroundColor = colors.home + SyncDeviceTypeButton.appearance().appearanceBackgroundColor = colors.header + + // Search + UIView.appearance(whenContainedInInstancesOf: [SearchViewController.self]).appearanceBackgroundColor = colors.home + InsetButton.appearance(whenContainedInInstancesOf: [SearchViewController.self]).appearanceBackgroundColor = .clear + + if #available(iOS 13.0, *) { + // Overrides all views inside of itself + // According to docs, UIWindow override should be enough, but some labels on iOS 13 are still messed up without UIView override as well + // (e.g. shields panel) + UIWindow.appearance().appearanceOverrideUserInterfaceStyle = isDark ? .dark : .light + UIView.appearance().appearanceOverrideUserInterfaceStyle = isDark ? .dark : .light + } else { + // iOS 12 fixes, many styling items do not work properly in iOS 12 + UILabel.appearance().appearanceTextColor = colors.tints.home + } + } +} + +extension UILabel { + @objc dynamic var appearanceTextColor: UIColor! { + get { return self.textColor } + set { self.textColor = newValue } + } +} + +extension UITableView { + @objc dynamic var appearanceSeparatorColor: UIColor? { + get { return self.separatorColor } + set { self.separatorColor = newValue } + } +} + +extension UIView { + @objc dynamic var appearanceBackgroundColor: UIColor? { + get { return self.backgroundColor } + set { self.backgroundColor = newValue } + } +} + +extension UITextField { + @objc dynamic var appearanceTextColor: UIColor? { + get { return self.textColor } + set { self.textColor = newValue } + } +} + +extension UIView { + @objc dynamic var appearanceOverrideUserInterfaceStyle: UIUserInterfaceStyle { + get { + if #available(iOS 13.0, *) { + return self.overrideUserInterfaceStyle + } + return .unspecified + } + set { + if #available(iOS 13.0, *) { + self.overrideUserInterfaceStyle = newValue + } + // Ignore + } + } +} + +extension UINavigationBar { + @objc dynamic var appearanceBarTintColor: UIColor? { + get { return self.barTintColor } + set { self.barTintColor = newValue } + } +} + diff --git a/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift b/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift index c66dad96cb6..19ee1beced9 100644 --- a/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift +++ b/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift @@ -36,7 +36,6 @@ class BasePasscodeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = SettingsUX.TableViewHeaderBackgroundColor updateRightBarButtonItem() } diff --git a/Client/Frontend/AuthenticationManager/PasscodeViews.swift b/Client/Frontend/AuthenticationManager/PasscodeViews.swift index a036d01e979..ef8c0af323a 100644 --- a/Client/Frontend/AuthenticationManager/PasscodeViews.swift +++ b/Client/Frontend/AuthenticationManager/PasscodeViews.swift @@ -142,7 +142,6 @@ class PasscodePane: UIView { init(title: String? = nil, passcodeSize: Int = 4) { codeInputView = PasscodeInputView(passcodeSize: passcodeSize) super.init(frame: .zero) - backgroundColor = SettingsUX.TableViewHeaderBackgroundColor titleLabel.text = title centerContainer.addSubview(titleLabel) diff --git a/Client/Frontend/Browser/BrowserTrayAnimators.swift b/Client/Frontend/Browser/BrowserTrayAnimators.swift index d24767574f3..dc5359494cb 100644 --- a/Client/Frontend/Browser/BrowserTrayAnimators.swift +++ b/Client/Frontend/Browser/BrowserTrayAnimators.swift @@ -77,7 +77,7 @@ private extension TrayToBrowserAnimator { cell.layer.borderWidth = 0.0 bvc.tabTrayDidDismiss(tabTray) - UIApplication.shared.windows.first?.backgroundColor = TabTrayControllerUX.BackgroundColor.colorFor(tabTray.privateMode ? .private : .regular) + UIApplication.shared.windows.first?.backgroundColor = tabTray.collectionView.backgroundColor tabTray.navigationController?.setNeedsStatusBarAppearanceUpdate() tabTray.toolbar.transform = CGAffineTransform(translationX: 0, y: UIConstants.BottomToolbarHeight) tabCollectionViewSnapshot.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) @@ -207,7 +207,7 @@ private extension BrowserToTrayAnimator { cell.frame = finalFrame cell.titleBackgroundView.transform = .identity cell.layoutIfNeeded() - UIApplication.shared.windows.first?.backgroundColor = TabTrayControllerUX.BackgroundColor.colorFor(tabTray.privateMode ? .private : .regular) + UIApplication.shared.windows.first?.backgroundColor = tabTray.collectionView.backgroundColor tabTray.navigationController?.setNeedsStatusBarAppearanceUpdate() cell.layer.borderWidth = TabTrayControllerUX.DefaultBorderWidth @@ -344,7 +344,7 @@ private func createTransitionCellFromTab(_ tab: Tab?, withFrame frame: CGRect) - } else { cell.favicon.image = #imageLiteral(resourceName: "defaultFavicon") } - cell.applyTheme(PrivateBrowsingManager.shared.isPrivateBrowsing ? .private : .regular) + cell.applyTheme(Theme.of(tab)) return cell } diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index 8bb1e6c601e..c33051c3938 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -35,7 +35,6 @@ private let KVOs: [KVOConstants] = [ ] private struct BrowserViewControllerUX { - fileprivate static let BackgroundColor = UIConstants.AppBackgroundColor fileprivate static let ShowHeaderTapAreaHeight: CGFloat = 32 fileprivate static let BookmarkStarAnimationDuration: Double = 0.5 fileprivate static let BookmarkStarAnimationOffset: CGFloat = 80 @@ -207,6 +206,7 @@ class BrowserViewController: UIViewController { // Observe some user preferences Preferences.Privacy.privateBrowsingOnly.observe(from: self) Preferences.General.tabBarVisibility.observe(from: self) + Preferences.General.themeNormalMode.observe(from: self) Preferences.General.alwaysRequestDesktopSite.observe(from: self) Preferences.Shields.allShields.forEach { $0.observe(from: self) } Preferences.Privacy.blockAllCookies.observe(from: self) @@ -245,12 +245,18 @@ class BrowserViewController: UIViewController { } override var preferredStatusBarStyle: UIStatusBarStyle { - switch Theme.of(tabManager.selectedTab) { - case .regular: - return .default - case .private: + let isDark = Theme.of(tabManager.selectedTab).isDark + if isDark { return .lightContent } + + // Light content, so using other status bar options + + if #available(iOS 13.0, *) { + return .darkContent + } + + return .default } func shouldShowFooterForTraitCollection(_ previousTraitCollection: UITraitCollection) -> Bool { @@ -634,8 +640,12 @@ class BrowserViewController: UIViewController { func presentOnboardingIntro() { if Preferences.General.basicOnboardingCompleted.value != OnboardingState.completed.rawValue { - guard let onboarding = OnboardingNavigationController(profile: profile, - onboardingType: .newUser) else { return } + guard let onboarding = OnboardingNavigationController( + profile: profile, + onboardingType: .newUser, + theme: Theme.of(tabManager.selectedTab) + ) else { return } + onboarding.onboardingDelegate = self present(onboarding, animated: true) } else { @@ -752,6 +762,7 @@ class BrowserViewController: UIViewController { let homePanelController = FavoritesViewController(profile: profile) homePanelController.delegate = self homePanelController.view.alpha = 0 + homePanelController.applyTheme(Theme.of(tabManager.selectedTab)) self.favoritesViewController = homePanelController @@ -1612,7 +1623,6 @@ extension BrowserViewController: TopToolbarDelegate { } func topToolbarDidTapBraveShieldsButton(_ topToolbar: TopToolbarView) { - // BRAVE TODO: Use actual instance guard let selectedTab = tabManager.selectedTab else { return } let shields = ShieldsViewController(tab: selectedTab) shields.shieldsSettingsChanged = { [unowned self] _ in @@ -1647,9 +1657,7 @@ extension BrowserViewController: TopToolbarDelegate { } func topToolbarDidTapMenuButton(_ topToolbar: TopToolbarView) { - let homePanel = MenuViewController(bvc: self, tab: tabManager.selectedTab) - let popover = PopoverController(contentController: homePanel, contentSizeBehavior: .preferredContentSize) - popover.present(from: topToolbar.menuButton, on: self) + tabToolbarDidPressMenu(topToolbar) } } @@ -1696,9 +1704,11 @@ extension BrowserViewController: ToolbarDelegate { } } - func tabToolbarDidPressMenu(_ tabToolbar: ToolbarProtocol, button: UIButton) { + func tabToolbarDidPressMenu(_ tabToolbar: ToolbarProtocol) { let homePanel = MenuViewController(bvc: self, tab: tabManager.selectedTab) let popover = PopoverController(contentController: homePanel, contentSizeBehavior: .preferredContentSize) + // Not dynamic, but trivial at this point, given how UI is currently setup + popover.color = Theme.of(tabManager.selectedTab).colors.home popover.present(from: tabToolbar.menuButton, on: self) } @@ -1994,9 +2004,9 @@ extension BrowserViewController: TabManagerDelegate { topToolbar.hideProgressBar() } - if tab.type != previous?.type { - let theme = Theme.of(tab) - applyTheme(theme) + let newTheme = Theme.of(tab) + if previous == nil || newTheme != Theme.of(previous) { + applyTheme(newTheme) } readerModeCache = ReaderMode.cache(for: tab) @@ -2889,10 +2899,16 @@ extension BrowserViewController: TabTrayDelegate { // MARK: Browser Chrome Theming extension BrowserViewController: Themeable { - + + var themeableChildren: [Themeable?]? { + return [topToolbar, toolbar, readerModeBar, tabsBar, favoritesViewController] + } + func applyTheme(_ theme: Theme) { - let ui: [Themeable?] = [topToolbar, toolbar, readerModeBar, tabsBar, favoritesViewController] - ui.forEach { $0?.applyTheme(theme) } + styleChildren(theme: theme) + + theme.applyAppearanceProperties() + statusBarOverlay.backgroundColor = topToolbar.backgroundColor setNeedsStatusBarAppearanceUpdate() } @@ -3020,6 +3036,8 @@ extension BrowserViewController: PreferencesObserver { switch key { case Preferences.General.tabBarVisibility.key: updateTabsBarVisibility() + case Preferences.General.themeNormalMode.key: + applyTheme(Theme.of(tabManager.selectedTab)) case Preferences.Privacy.privateBrowsingOnly.key: let isPrivate = Preferences.Privacy.privateBrowsingOnly.value switchToPrivacyMode(isPrivate: isPrivate) diff --git a/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift b/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift index fa93b10dd4c..5d0dca0c043 100644 --- a/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift +++ b/Client/Frontend/Browser/HomePanel/BraveShieldStatsView.swift @@ -8,7 +8,13 @@ import BraveShared class BraveShieldStatsView: UIView, Themeable { func applyTheme(_ theme: Theme) { - // BRAVE TODO: + styleChildren(theme: theme) + + let colors = theme.colors.stats + adsStatView.color = colors.ads + httpsStatView.color = colors.httpse + timeStatView.color = colors.timeSaved + } fileprivate let millisecondsPerItem: Int = 50 @@ -16,21 +22,18 @@ class BraveShieldStatsView: UIView, Themeable { lazy var adsStatView: StatView = { let statView = StatView(frame: CGRect.zero) statView.title = Strings.ShieldsAdAndTrackerStats - statView.color = UX.BraveOrange return statView }() lazy var httpsStatView: StatView = { let statView = StatView(frame: CGRect.zero) statView.title = Strings.ShieldsHttpsStats - statView.color = UX.Green return statView }() lazy var timeStatView: StatView = { let statView = StatView(frame: CGRect.zero) statView.title = Strings.ShieldsTimeStats - statView.color = PrivateBrowsingManager.shared.isPrivateBrowsing ? UX.GreyA : UX.GreyJ return statView }() diff --git a/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift b/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift index 75bde2301ce..eb897ed4ba6 100644 --- a/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift +++ b/Client/Frontend/Browser/HomePanel/FavoritesViewController.swift @@ -105,8 +105,6 @@ class FavoritesViewController: UIViewController, Themeable { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = PrivateBrowsingManager.shared.isPrivateBrowsing ? UX.HomePanel.BackgroundColorPBM : UX.HomePanel.BackgroundColor - let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(gesture:))) collection.addGestureRecognizer(longPressGesture) @@ -202,10 +200,14 @@ class FavoritesViewController: UIViewController, Themeable { updateDuckDuckGoVisibility() } + var themeableChildren: [Themeable?]? { + return [braveShieldStatsView] + } + func applyTheme(_ theme: Theme) { - let isPrivate = theme == .private - view.backgroundColor = isPrivate ? UX.HomePanel.BackgroundColorPBM : UX.HomePanel.BackgroundColor - braveShieldStatsView.timeStatView.color = isPrivate ? UX.GreyA : UX.GreyJ + styleChildren(theme: theme) + + view.backgroundColor = theme.colors.home } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { diff --git a/Client/Frontend/Browser/Onboarding/OnboardingNavigationController.swift b/Client/Frontend/Browser/Onboarding/OnboardingNavigationController.swift index fd17d38fa53..7a480950fbd 100644 --- a/Client/Frontend/Browser/Onboarding/OnboardingNavigationController.swift +++ b/Client/Frontend/Browser/Onboarding/OnboardingNavigationController.swift @@ -47,12 +47,12 @@ class OnboardingNavigationController: UINavigationController { case shieldsInfo /// Returns new ViewController associated with the screen type - func viewController(with profile: Profile) -> OnboardingViewController { + func viewController(with profile: Profile, theme: Theme) -> OnboardingViewController { switch self { case .searchEnginePicker: - return OnboardingSearchEnginesViewController(profile: profile) + return OnboardingSearchEnginesViewController(profile: profile, theme: theme) case .shieldsInfo: - return OnboardingShieldsViewController(profile: profile) + return OnboardingShieldsViewController(profile: profile, theme: theme) } } @@ -66,10 +66,10 @@ class OnboardingNavigationController: UINavigationController { private(set) var onboardingType: OnboardingType? - convenience init?(profile: Profile, onboardingType: OnboardingType) { + convenience init?(profile: Profile, onboardingType: OnboardingType, theme: Theme) { guard let firstScreen = onboardingType.screens.first else { return nil } - let firstViewController = firstScreen.viewController(with: profile) + let firstViewController = firstScreen.viewController(with: profile, theme: theme) self.init(rootViewController: firstViewController) self.onboardingType = onboardingType firstViewController.delegate = self @@ -93,7 +93,7 @@ extension OnboardingNavigationController: Onboardable { let index = allScreens.firstIndex { $0.type == type(of: current) } guard let nextIndex = index?.advanced(by: 1), - let nextScreen = allScreens[safe: nextIndex]?.viewController(with: current.profile) else { + let nextScreen = allScreens[safe: nextIndex]?.viewController(with: current.profile, theme: current.theme) else { log.info("Last screen reached, onboarding is complete") onboardingDelegate?.onboardingCompleted(self) return diff --git a/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesView.swift b/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesView.swift index 47a3b06d42c..010a96eb433 100644 --- a/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesView.swift +++ b/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesView.swift @@ -20,8 +20,6 @@ extension OnboardingSearchEnginesViewController { static let rowHeight: CGFloat = 54 static let imageSize: CGFloat = 32 static let cornerRadius: CGFloat = 8 - static let selectedBackgroundColor = #colorLiteral(red: 0.9411764706, green: 0.9450980392, blue: 1, alpha: 1) - static let deselectedBackgroundColor: UIColor = .white } } @@ -74,7 +72,6 @@ extension OnboardingSearchEnginesViewController { init() { super.init(frame: .zero) - backgroundColor = .white addSubview(braveLogo) @@ -186,26 +183,26 @@ extension OnboardingSearchEnginesViewController { get { return imageView?.image } } + var selectedBackgroundColor: UIColor? { + didSet { + selectedBackgroundView?.backgroundColor = selectedBackgroundColor + } + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) imageView?.contentMode = .scaleAspectFit layer.cornerRadius = UX.SearchEngineCell.cornerRadius - selectionStyle = .none + + selectedBackgroundView = UIView().then { + $0.layer.cornerRadius = UX.SearchEngineCell.cornerRadius + } } @available(*, unavailable) required init(coder: NSCoder) { fatalError() } - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - backgroundColor = selected ? - UX.SearchEngineCell.selectedBackgroundColor : UX.SearchEngineCell.deselectedBackgroundColor - - textLabel?.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium) - } - override func layoutSubviews() { super.layoutSubviews() let size = UX.SearchEngineCell.imageSize diff --git a/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesViewController.swift b/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesViewController.swift index 547a9187c1c..7f977b97269 100644 --- a/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesViewController.swift +++ b/Client/Frontend/Browser/Onboarding/OnboardingSearchEnginesViewController.swift @@ -14,7 +14,9 @@ class OnboardingSearchEnginesViewController: OnboardingViewController { static let spaceBetweenRows: CGFloat = 8 } - let searchEngines: SearchEngines + var searchEngines: SearchEngines { + profile.searchEngines + } private var contentView: View { return view as! View // swiftlint:disable:this force_cast @@ -23,13 +25,6 @@ class OnboardingSearchEnginesViewController: OnboardingViewController { override func loadView() { view = View() } - - override init(profile: Profile) { - self.searchEngines = profile.searchEngines - super.init(profile: profile) - - //super.init(nibName: nil, bundle: nil) - } override func viewDidLoad() { super.viewDidLoad() @@ -39,6 +34,12 @@ class OnboardingSearchEnginesViewController: OnboardingViewController { contentView.continueButton.addTarget(self, action: #selector(continueTapped), for: .touchDown) contentView.skipButton.addTarget(self, action: #selector(skipTapped), for: .touchDown) + + // This is kind of stupid, but for some reason the table's background color will not + // go away or be forced to .clear, so just mimicing + let tablebackground = UIView() + tablebackground.backgroundColor = contentView.backgroundColor + contentView.searchEnginesTable.backgroundView = tablebackground } @objc override func continueTapped() { @@ -97,6 +98,7 @@ extension OnboardingSearchEnginesViewController: UITableViewDataSource { cell.searchEngineName = searchEngine.shortName cell.searchEngineImage = searchEngine.image + cell.selectedBackgroundColor = theme.colors.accent if searchEngine == defaultEngine { tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) diff --git a/Client/Frontend/Browser/Onboarding/OnboardingShieldsView.swift b/Client/Frontend/Browser/Onboarding/OnboardingShieldsView.swift index 8ffe88b253e..737a67e9536 100644 --- a/Client/Frontend/Browser/Onboarding/OnboardingShieldsView.swift +++ b/Client/Frontend/Browser/Onboarding/OnboardingShieldsView.swift @@ -39,7 +39,6 @@ extension OnboardingShieldsViewController { } private let descriptionView = UIView().then { - $0.backgroundColor = .white $0.layer.cornerRadius = 12 $0.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } @@ -68,6 +67,13 @@ extension OnboardingShieldsViewController { $0.distribution = .equalCentering } + override var backgroundColor: UIColor? { + didSet { + // Needed to support rounding + descriptionView.backgroundColor = backgroundColor + } + } + init() { super.init(frame: .zero) diff --git a/Client/Frontend/Browser/Onboarding/OnboardingViewController.swift b/Client/Frontend/Browser/Onboarding/OnboardingViewController.swift index d1d9ddcff75..87e5b47838c 100644 --- a/Client/Frontend/Browser/Onboarding/OnboardingViewController.swift +++ b/Client/Frontend/Browser/Onboarding/OnboardingViewController.swift @@ -10,15 +10,22 @@ import Shared class OnboardingViewController: UIViewController { weak var delegate: Onboardable? var profile: Profile + let theme: Theme - init(profile: Profile) { + init(profile: Profile, theme: Theme) { self.profile = profile + self.theme = theme super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init(coder: NSCoder) { fatalError() } + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = theme.colors.home + } + /// Default behavior to present next onboarding screen. /// Override it to add custom behavior. @objc func continueTapped() { diff --git a/Client/Frontend/Browser/ReaderModeBarView.swift b/Client/Frontend/Browser/ReaderModeBarView.swift index e23b73e96bf..0502bb3d621 100644 --- a/Client/Frontend/Browser/ReaderModeBarView.swift +++ b/Client/Frontend/Browser/ReaderModeBarView.swift @@ -89,7 +89,9 @@ class ReaderModeBarView: UIView { extension ReaderModeBarView: Themeable { func applyTheme(_ theme: Theme) { - backgroundColor = UIColor.Browser.Background.colorFor(theme) - buttonTintColor = UIColor.Browser.Tint.colorFor(theme) + styleChildren(theme: theme) + + backgroundColor = theme.colors.home + buttonTintColor = theme.colors.tints.home } } diff --git a/Client/Frontend/Browser/Search/SearchSuggestionsPromptView.swift b/Client/Frontend/Browser/Search/SearchSuggestionsPromptView.swift index 22c7e957b7b..a122f30dbaa 100644 --- a/Client/Frontend/Browser/Search/SearchSuggestionsPromptView.swift +++ b/Client/Frontend/Browser/Search/SearchSuggestionsPromptView.swift @@ -16,7 +16,6 @@ class SearchSuggestionPromptView: UIView { static let PromptYesFont = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.bold) static let PromptNoFont = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.regular) static let PromptInsets = UIEdgeInsets(top: 15, left: 12, bottom: 15, right: 12) - static let PromptButtonColor = BraveUX.Blue } init(optionSelected: @escaping (Bool) -> Void) { @@ -24,8 +23,6 @@ class SearchSuggestionPromptView: UIView { super.init(frame: .zero) - backgroundColor = UX.PromptColor - let promptBottomBorder = UIView() promptBottomBorder.backgroundColor = BraveUX.GreyD addSubview(promptBottomBorder) @@ -44,7 +41,6 @@ class SearchSuggestionPromptView: UIView { let promptYesButton = InsetButton() promptYesButton.setTitle(Strings.Yes, for: .normal) - promptYesButton.setTitleColor(UX.PromptButtonColor, for: .normal) promptYesButton.titleLabel?.font = UX.PromptYesFont promptYesButton.titleEdgeInsets = UX.PromptInsets // If the prompt message doesn't fit, this prevents it from pushing the buttons @@ -55,7 +51,6 @@ class SearchSuggestionPromptView: UIView { let promptNoButton = InsetButton() promptNoButton.setTitle(Strings.No, for: .normal) - promptNoButton.setTitleColor(UX.PromptButtonColor, for: .normal) promptNoButton.titleLabel?.font = UX.PromptNoFont promptNoButton.titleEdgeInsets = UX.PromptInsets // If the prompt message doesn't fit, this prevents it from pushing the buttons diff --git a/Client/Frontend/Browser/Search/SearchViewController.swift b/Client/Frontend/Browser/Search/SearchViewController.swift index 057fa0a627c..11db4574ce2 100644 --- a/Client/Frontend/Browser/Search/SearchViewController.swift +++ b/Client/Frontend/Browser/Search/SearchViewController.swift @@ -82,7 +82,6 @@ class SearchViewController: SiteTableViewController, KeyboardHelperDelegate, Loa } override func viewDidLoad() { - view.backgroundColor = UIConstants.PanelBackgroundColor let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light)) view.addSubview(blur) @@ -90,7 +89,6 @@ class SearchViewController: SiteTableViewController, KeyboardHelperDelegate, Loa KeyboardHelper.defaultHelper.addDelegate(self) - searchEngineScrollView.layer.backgroundColor = SearchViewControllerUX.SearchEngineScrollViewBackgroundColor searchEngineScrollView.layer.shadowRadius = 0 searchEngineScrollView.layer.shadowOpacity = 100 searchEngineScrollView.layer.shadowOffset = CGSize(width: 0, height: -SearchViewControllerUX.SearchEngineTopBorderWidth) @@ -769,8 +767,6 @@ fileprivate class SuggestionButton: InsetButton { setTitleColor(UIConstants.HighlightBlue, for: []) setTitleColor(UIColor.Photon.White100, for: .highlighted) titleLabel?.font = DynamicFontHelper.defaultHelper.DefaultMediumFont - backgroundColor = SearchViewControllerUX.SuggestionBackgroundColor - layer.borderColor = SearchViewControllerUX.SuggestionBorderColor.cgColor layer.borderWidth = SearchViewControllerUX.SuggestionBorderWidth layer.cornerRadius = SearchViewControllerUX.SuggestionCornerRadius contentEdgeInsets = SearchViewControllerUX.SuggestionInsets diff --git a/Client/Frontend/Browser/TabManager.swift b/Client/Frontend/Browser/TabManager.swift index 576684780bd..68bdeabcb8c 100644 --- a/Client/Frontend/Browser/TabManager.swift +++ b/Client/Frontend/Browser/TabManager.swift @@ -243,13 +243,6 @@ class TabManager: NSObject { } if let tab = selectedTab { TabEvent.post(.didGainFocus, for: tab) - - switch tab.type { - case .regular: - UITextField.appearance().keyboardAppearance = .light - case .private: - UITextField.appearance().keyboardAppearance = .dark - } } guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, diff --git a/Client/Frontend/Browser/TabTrayButtonExtensions.swift b/Client/Frontend/Browser/TabTrayButtonExtensions.swift index 879efd3f415..29557705ae1 100644 --- a/Client/Frontend/Browser/TabTrayButtonExtensions.swift +++ b/Client/Frontend/Browser/TabTrayButtonExtensions.swift @@ -6,11 +6,18 @@ import UIKit import Shared class PrivateModeButton: InsetButton, Themeable { - var light: Bool = false - override var isSelected: Bool { didSet { - backgroundColor = isSelected ? UIColor.Photon.Purple60 : .clear + accessibilityValue = isSelected ? Strings.TabPrivateModeToggleAccessibilityValueOn : Strings.TabPrivateModeToggleAccessibilityValueOff + backgroundColor = isSelected ? selectedBackgroundColor : .clear + } + } + + var selectedBackgroundColor: UIColor? { + didSet { + if isSelected { + backgroundColor = selectedBackgroundColor + } } } @@ -28,10 +35,11 @@ class PrivateModeButton: InsetButton, Themeable { } func applyTheme(_ theme: Theme) { - setTitleColor(UIColor.TabTray.ToolbarButtonTint.colorFor(theme), for: .normal) + styleChildren(theme: theme) + + setTitleColor(theme.colors.tints.footer, for: .normal) imageView?.tintColor = tintColor - isSelected = theme.isPrivate - accessibilityValue = isSelected ? Strings.TabPrivateModeToggleAccessibilityValueOn : Strings.TabPrivateModeToggleAccessibilityValueOff + selectedBackgroundColor = theme.colors.accent } } @@ -43,12 +51,3 @@ extension UIButton { return newTab } } - -extension TabsButton { - static func tabTrayButton() -> TabsButton { - let tabsButton = TabsButton() - tabsButton.countLabel.text = "0" - tabsButton.accessibilityLabel = Strings.Show_Tabs - return tabsButton - } -} diff --git a/Client/Frontend/Browser/TabTrayController.swift b/Client/Frontend/Browser/TabTrayController.swift index 63d7e0791a4..53522b487f4 100644 --- a/Client/Frontend/Browser/TabTrayController.swift +++ b/Client/Frontend/Browser/TabTrayController.swift @@ -11,8 +11,6 @@ import BraveShared struct TabTrayControllerUX { static let CornerRadius = CGFloat(6.0) static let DefaultBorderWidth = 1.0 / UIScreen.main.scale - static let BackgroundColor = UIColor.TopTabs.Background - static let CellBackgroundColor = UIColor.TopTabs.Background static let ToolbarFont = UIFont.systemFont(ofSize: 17.0, weight: .medium) static let TextBoxHeight = CGFloat(32.0) static let FaviconSize = CGFloat(20) @@ -194,11 +192,11 @@ class TabCell: UICollectionViewCell, Themeable { } func applyTheme(_ theme: Theme) { - backgroundHolder.backgroundColor = theme == .private ? UX.HomePanel.BackgroundColorPBM : UX.HomePanel.BackgroundColor + styleChildren(theme: theme) + + backgroundHolder.backgroundColor = theme.colors.home screenshotView.backgroundColor = backgroundHolder.backgroundColor - if theme == .private { - favicon.tintColor = UIColor.Photon.White100 - } + favicon.tintColor = theme.colors.tints.home } } @@ -228,10 +226,15 @@ class TabTrayController: UIViewController, Themeable { fileprivate(set) internal var privateMode: Bool = false { didSet { + // Should be set immediately before other logic executes PrivateBrowsingManager.shared.isPrivateBrowsing = privateMode - tabDataSource.tabs = tabManager.tabsForCurrentMode - applyTheme(privateMode ? .private : .regular) + + // This is a little tricky since this menu is one of the only places inside of the appliation + // that has a state without gauranteeing a tab exists. Most UI elements should use `theme` or at the least + // the related tab's theme, here this is not possible, so using hardened values 😕😭 + applyTheme(Theme.of(nil)) + toolbar.privateModeButton.isSelected = privateMode collectionView?.reloadData() setNeedsStatusBarAppearanceUpdate() } @@ -291,7 +294,7 @@ class TabTrayController: UIViewController, Themeable { self.collectionView.reloadData() } -// MARK: View Controller Callbacks + // MARK: View Controller Callbacks override func viewDidLoad() { super.viewDidLoad() @@ -337,7 +340,7 @@ class TabTrayController: UIViewController, Themeable { NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(dynamicFontChanged), name: .DynamicFontChanged, object: nil) - applyTheme(privateMode ? .private : .regular) + applyTheme(Theme.of(tabManager.selectedTab)) } override func viewWillAppear(_ animated: Bool) { @@ -393,10 +396,14 @@ class TabTrayController: UIViewController, Themeable { } } + var themeableChildren: [Themeable?]? { + let cells = collectionView?.visibleCells.compactMap({ $0 as? TabCell }) ?? [] + return [toolbar] + cells + } + func applyTheme(_ theme: Theme) { - collectionView?.backgroundColor = TabTrayControllerUX.BackgroundColor.colorFor(theme) - collectionView?.visibleCells.compactMap({ $0 as? TabCell }).forEach { $0.applyTheme(theme) } - toolbar.applyTheme(theme) + styleChildren(theme: theme) + collectionView?.backgroundColor = theme.colors.home } /// Reset the empty private browsing state (hide the details, unhide the learn more button) if it was changed @@ -458,10 +465,9 @@ class TabTrayController: UIViewController, Themeable { tabManager.willSwitchTabMode(leavingPBM: privateMode) privateMode = !privateMode - //When we switch from Private => Regular make sure we reset _selectedIndex, fix for bug #888 + // When we switch from Private => Regular make sure we reset _selectedIndex, fix for bug #888 tabManager.resetSelectedIndex() - - toolbar.privateModeButton.isSelected = privateMode + collectionView.layoutSubviews() let toView: UIView @@ -805,7 +811,7 @@ fileprivate class TabManagerDataSource: NSObject, UICollectionViewDataSource { } tabCell.screenshotView.image = tab.screenshot - tabCell.applyTheme(PrivateBrowsingManager.shared.isPrivateBrowsing ? .private : .regular) + tabCell.applyTheme(Theme.of(tab)) return tabCell } @@ -1105,7 +1111,7 @@ extension TabTrayController: UIAdaptivePresentationControllerDelegate, UIPopover } // MARK: - Toolbar -class TrayToolbar: UIView { +class TrayToolbar: UIView, Themeable { fileprivate let toolbarButtonSize = CGSize(width: 44, height: 44) let addTabButton = UIButton(type: .system).then { @@ -1160,12 +1166,19 @@ class TrayToolbar: UIView { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + var themeableChildren: [Themeable?]? { + return [privateModeButton] + } - fileprivate func applyTheme(_ theme: Theme) { - UIApplication.shared.windows.first?.backgroundColor = TabTrayControllerUX.BackgroundColor.colorFor(theme) - addTabButton.tintColor = UIColor.TabTray.ToolbarButtonTint.colorFor(theme) // Needs to be changed - doneButton.tintColor = UIColor.TabTray.ToolbarButtonTint.colorFor(theme) - backgroundColor = TabTrayControllerUX.BackgroundColor.colorFor(theme) - privateModeButton.applyTheme(theme) + func applyTheme(_ theme: Theme) { + styleChildren(theme: theme) + + backgroundColor = theme.colors.home + UIApplication.shared.windows.first?.backgroundColor = backgroundColor + + addTabButton.tintColor = theme.colors.tints.footer + doneButton.tintColor = addTabButton.tintColor + } } diff --git a/Client/Frontend/Browser/TabsBar/TabBarCell.swift b/Client/Frontend/Browser/TabsBar/TabBarCell.swift index 1e81d765f52..2712ac00009 100644 --- a/Client/Frontend/Browser/TabsBar/TabBarCell.swift +++ b/Client/Frontend/Browser/TabsBar/TabBarCell.swift @@ -90,19 +90,21 @@ class TabBarCell: UICollectionViewCell { override var isSelected: Bool { didSet(selected) { - closeButton.tintColor = PrivateBrowsingManager.shared.isPrivateBrowsing ? UIColor.white : UIColor.black + let theme = Theme.of(tab) + closeButton.tintColor = theme.colors.tints.header + if selected { titleLabel.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.semibold) closeButton.isHidden = false - titleLabel.textColor = PrivateBrowsingManager.shared.isPrivateBrowsing ? UIColor.white : UIColor.black - backgroundColor = PrivateBrowsingManager.shared.isPrivateBrowsing ? BraveUX.DarkToolbarsBackgroundSolidColor : BraveUX.ToolbarsBackgroundSolidColor + titleLabel.textColor = theme.colors.tints.header + backgroundColor = theme.colors.home } // Prevent swipe and release outside- deselects cell. else if currentIndex != tabManager?.currentDisplayedIndex { titleLabel.font = UIFont.systemFont(ofSize: 12) - titleLabel.textColor = PrivateBrowsingManager.shared.isPrivateBrowsing ? UIColor(white: 1.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.4) + titleLabel.textColor = theme.colors.tints.header.withAlphaComponent(0.4) closeButton.isHidden = true - backgroundColor = .clear + backgroundColor = theme.colors.header } } } diff --git a/Client/Frontend/Browser/TabsBar/TabsBarViewController.swift b/Client/Frontend/Browser/TabsBar/TabsBarViewController.swift index 1b8fd55a174..7c983835bf1 100644 --- a/Client/Frontend/Browser/TabsBar/TabsBarViewController.swift +++ b/Client/Frontend/Browser/TabsBar/TabsBarViewController.swift @@ -205,8 +205,10 @@ class TabsBarViewController: UIViewController { private func addScrollHint(for side: HintSide, maskLayer: CAGradientLayer) { maskLayer.removeFromSuperlayer() - let barsColor = PrivateBrowsingManager.shared.isPrivateBrowsing ? - UX.barsDarkBackgroundSolidColor : UX.barsBackgroundSolidColor + guard let barsColor = collectionView.backgroundColor ?? view.backgroundColor else { + // If not setup now, will be at some point, and then this can be flushed + return + } let colors = [barsColor.withAlphaComponent(0).cgColor, barsColor.cgColor] let locations = [0.9, 1.0] @@ -333,17 +335,15 @@ extension TabsBarViewController: TabManagerDelegate { extension TabsBarViewController: Themeable { func applyTheme(_ theme: Theme) { - switch theme { - case .regular: - view.backgroundColor = BraveUX.GreyB - plusButton.tintColor = BraveUX.GreyI - bottomLine.backgroundColor = UIColor(white: 0.0, alpha: 0.2) - case .private: - view.backgroundColor = BraveUX.Black - plusButton.tintColor = UIColor.white - bottomLine.backgroundColor = UIColor(white: 1.0, alpha: 0.2) - } - + styleChildren(theme: theme) + + view.backgroundColor = theme.colors.header + plusButton.tintColor = theme.colors.tints.header + bottomLine.backgroundColor = theme.colors.border.withAlphaComponent(theme.colors.transparencies.borderAlpha) collectionView.backgroundColor = view.backgroundColor + // Updates overflow colors too + overflowIndicators() + + collectionView.reloadData() } } diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift index 253a71e32b1..2437837c6ca 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/BottomToolbarView.swift @@ -10,12 +10,12 @@ import BraveShared class BottomToolbarView: UIView, ToolbarProtocol { weak var tabToolbarDelegate: ToolbarDelegate? - let tabsButton = TabsButton() - let forwardButton = ToolbarButton() - let backButton = ToolbarButton() - let shareButton = ToolbarButton() - let addTabButton = ToolbarButton() - let menuButton = ToolbarButton() + let tabsButton = TabsButton(top: false) + let forwardButton = ToolbarButton(top: false) + let backButton = ToolbarButton(top: false) + let shareButton = ToolbarButton(top: false) + let addTabButton = ToolbarButton(top: false) + let menuButton = ToolbarButton(top: false) let actionButtons: [Themeable & UIButton] var helper: ToolbarHelper? @@ -104,14 +104,12 @@ class BottomToolbarView: UIView, ToolbarProtocol { // MARK: - Themeable extension BottomToolbarView: Themeable { + var themeableChildren: [Themeable?]? { + return actionButtons + } + func applyTheme(_ theme: Theme) { - switch theme { - case .regular: - backgroundColor = BraveUX.ToolbarsBackgroundSolidColor - case .private: - backgroundColor = BraveUX.DarkToolbarsBackgroundSolidColor - } - - helper?.setTheme(theme: theme, forButtons: actionButtons) + styleChildren(theme: theme) + backgroundColor = theme.colors.footer } } diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditHeaderView.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditHeaderView.swift index 3706886ac90..a32926689c8 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditHeaderView.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/AddEditHeaderView.swift @@ -22,8 +22,6 @@ class AddEditHeaderView: UIView { override init(frame: CGRect) { super.init(frame: frame) - - backgroundColor = .white addSubview(mainStackView) mainStackView.snp.makeConstraints { diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift index 35c572af540..c08e81244d4 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/Bookmarks/BookmarksViewController.swift @@ -22,7 +22,6 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco lazy var editBookmarksButton = UIBarButtonItem().then { $0.image = #imageLiteral(resourceName: "edit").template $0.style = .plain - $0.tintColor = BraveUX.LightBlue $0.target = self $0.action = #selector(onEditBookmarksButton) } @@ -30,7 +29,6 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco lazy var addFolderButton = UIBarButtonItem().then { $0.image = #imageLiteral(resourceName: "bookmarks_newfolder_icon").template $0.style = .plain - $0.tintColor = BraveUX.LightBlue $0.target = self $0.action = #selector(onAddBookmarksFolderButton) } @@ -62,7 +60,6 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = BraveUX.BackgroundColorForSideToolbars tableView.allowsSelectionDuringEditing = true setUpToolbar() @@ -85,10 +82,10 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco let items = [padding, addFolderButton, flexibleSpace, editBookmarksButton, padding] setToolbarItems(items, animated: true) - navigationController?.toolbar.do { - $0.barTintColor = BraveUX.BackgroundColorForSideToolbars - $0.isTranslucent = false - } +// navigationController?.toolbar.do { +// $0.barTintColor = BraveUX.BackgroundColorForSideToolbars +// $0.isTranslucent = false +// } } override func reloadData() { @@ -198,6 +195,7 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco cell.addGestureRecognizer(lp) } + cell.backgroundColor = .clear cell.imageView?.contentMode = .scaleAspectFit cell.imageView?.image = FaviconFetcher.defaultFavicon cell.imageView?.layer.cornerRadius = 6 @@ -220,8 +218,6 @@ class BookmarksViewController: SiteTableViewController, ToolbarUrlActionsProtoco cell.textLabel?.text = item.displayTitle ?? item.url cell.textLabel?.lineBreakMode = .byTruncatingTail - cell.contentView.backgroundColor = .white - if !item.isFolder { configCell(icon: item.domain?.favicon) cell.textLabel?.font = UIFont.systemFont(ofSize: fontSize) diff --git a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift index f451b59e1c4..4ae38a25d7e 100644 --- a/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift +++ b/Client/Frontend/Browser/Toolbars/BottomToolbar/Menu/MenuViewController.swift @@ -26,7 +26,6 @@ private class MenuCell: UITableViewCell { $0.trailing.equalTo(self).inset(12) $0.leading.equalTo(iconView.snp.trailing) } - labelView.textColor = .black separatorInset = UIEdgeInsets(top: 0, left: iconLength, bottom: 0, right: 0) } @available(*, unavailable) @@ -67,12 +66,12 @@ class MenuViewController: UITableViewController { var icon: UIImage { switch self { - case .bookmarks: return #imageLiteral(resourceName: "menu_bookmarks") - case .history: return #imageLiteral(resourceName: "menu-history") - case .settings: return #imageLiteral(resourceName: "menu-settings") - case .add: return #imageLiteral(resourceName: "menu-add-bookmark") - case .share: return #imageLiteral(resourceName: "nav-share") - case .downloads: return #imageLiteral(resourceName: "menu-downloads") + case .bookmarks: return #imageLiteral(resourceName: "menu_bookmarks").template + case .history: return #imageLiteral(resourceName: "menu-history").template + case .settings: return #imageLiteral(resourceName: "menu-settings").template + case .add: return #imageLiteral(resourceName: "menu-add-bookmark").template + case .share: return #imageLiteral(resourceName: "nav-share").template + case .downloads: return #imageLiteral(resourceName: "menu-downloads").template } } } @@ -114,6 +113,7 @@ class MenuViewController: UITableViewController { tableView.separatorColor = UX.separatorColor tableView.rowHeight = UX.rowHeight + tableView.backgroundColor = .clear tableView.contentInset = UIEdgeInsets(top: UX.topBottomInset, left: 0, bottom: UX.topBottomInset, right: 0) @@ -125,10 +125,6 @@ class MenuViewController: UITableViewController { tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 1)) - // TODO: Make the background view transparent with alpha 0.6 - // simple setting its alpha doesn't seem to work. - tableView.backgroundColor = #colorLiteral(red: 0.9529411765, green: 0.9529411765, blue: 0.9647058824, alpha: 1) - let size = CGSize(width: 200, height: UIScreen.main.bounds.height) let fit = view.systemLayoutSizeFitting( @@ -172,12 +168,26 @@ class MenuViewController: UITableViewController { cell.labelView.text = button.title cell.iconView.image = button.icon + + let homeColor = Theme.of(tab).colors.tints.home + cell.iconView.tintColor = homeColor.withAlphaComponent(0.6) + cell.labelView.textColor = homeColor cell.tag = button.rawValue cell.backgroundColor = .clear return cell } + override func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + cell?.contentView.backgroundColor = Theme.of(tab).colors.home.withAlphaComponent(0.5) + } + + override func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + cell?.contentView.backgroundColor = .clear + } + // MARK: - Actions private enum DoneButtonPosition { case left, right } diff --git a/Client/Frontend/Browser/Toolbars/ToolbarButton.swift b/Client/Frontend/Browser/Toolbars/ToolbarButton.swift index 9378ec9349b..c7923466e88 100644 --- a/Client/Frontend/Browser/Toolbars/ToolbarButton.swift +++ b/Client/Frontend/Browser/Toolbars/ToolbarButton.swift @@ -5,17 +5,21 @@ import UIKit class ToolbarButton: UIButton { - var selectedTintColor: UIColor! - var unselectedTintColor: UIColor! - var disabledTintColor: UIColor! + fileprivate var selectedTintColor: UIColor? + fileprivate var primaryTintColor: UIColor? + fileprivate var disabledTintColor: UIColor? - override init(frame: CGRect) { - super.init(frame: frame) + let top: Bool + + required init(top: Bool) { + self.top = top + super.init(frame: .zero) adjustsImageWhenHighlighted = false - selectedTintColor = tintColor - unselectedTintColor = tintColor - disabledTintColor = UIColor.Photon.Grey50 - imageView?.contentMode = .scaleAspectFit //exc bad access + imageView?.contentMode = .scaleAspectFit + } + + override init(frame: CGRect) { + fatalError("init(coder:) has not been implemented") } required init?(coder aDecoder: NSCoder) { @@ -24,13 +28,13 @@ class ToolbarButton: UIButton { override open var isHighlighted: Bool { didSet { - self.tintColor = isHighlighted ? selectedTintColor : unselectedTintColor + self.tintColor = isHighlighted ? selectedTintColor : primaryTintColor } } override open var isEnabled: Bool { didSet { - self.tintColor = isEnabled ? unselectedTintColor : disabledTintColor + self.tintColor = isEnabled ? primaryTintColor : disabledTintColor } } @@ -44,10 +48,13 @@ class ToolbarButton: UIButton { extension ToolbarButton: Themeable { func applyTheme(_ theme: Theme) { - selectedTintColor = UIColor.ToolbarButton.SelectedTint.colorFor(theme) - disabledTintColor = UIColor.ToolbarButton.DisabledTint.colorFor(theme) - unselectedTintColor = UIColor.Browser.Tint.colorFor(theme) - tintColor = isEnabled ? unselectedTintColor : disabledTintColor - imageView?.tintColor = tintColor + styleChildren(theme: theme) + + selectedTintColor = theme.colors.accent + primaryTintColor = top ? theme.colors.tints.header : theme.colors.tints.footer + disabledTintColor = primaryTintColor?.withAlphaComponent(0.4) + + // Logic is slightly weird, but necessary for proper styling at launch + tintColor = isEnabled ? primaryTintColor : disabledTintColor } } diff --git a/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift b/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift index a3fdbe6a320..e20364b5354 100644 --- a/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift +++ b/Client/Frontend/Browser/Toolbars/ToolbarHelper.swift @@ -9,10 +9,6 @@ import Shared class ToolbarHelper: NSObject { let toolbar: ToolbarProtocol - func setTheme(theme: Theme, forButtons buttons: [Themeable]) { - buttons.forEach { $0.applyTheme(theme) } - } - init(toolbar: ToolbarProtocol) { self.toolbar = toolbar super.init() @@ -45,12 +41,10 @@ class ToolbarHelper: NSObject { let longPressGestureForwardButton = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressForward)) toolbar.forwardButton.addGestureRecognizer(longPressGestureForwardButton) toolbar.forwardButton.addTarget(self, action: #selector(didClickForward), for: .touchUpInside) - - setTheme(theme: .regular, forButtons: toolbar.actionButtons) } func didClickMenu() { - toolbar.tabToolbarDelegate?.tabToolbarDidPressMenu(toolbar, button: toolbar.backButton) + toolbar.tabToolbarDelegate?.tabToolbarDidPressMenu(toolbar) } func didClickBack() { diff --git a/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift b/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift index 85aa473ca86..0e7cc14da1e 100644 --- a/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift +++ b/Client/Frontend/Browser/Toolbars/ToolbarProtocols.swift @@ -44,7 +44,7 @@ protocol ToolbarDelegate: class { func tabToolbarDidLongPressBack(_ tabToolbar: ToolbarProtocol, button: UIButton) func tabToolbarDidLongPressForward(_ tabToolbar: ToolbarProtocol, button: UIButton) func tabToolbarDidPressTabs(_ tabToolbar: ToolbarProtocol, button: UIButton) - func tabToolbarDidPressMenu(_ tabToolbar: ToolbarProtocol, button: UIButton) + func tabToolbarDidPressMenu(_ tabToolbar: ToolbarProtocol) func tabToolbarDidLongPressTabs(_ tabToolbar: ToolbarProtocol, button: UIButton) func tabToolbarDidPressShare() func tabToolbarDidPressAddTab(_ tabToolbar: ToolbarProtocol, button: UIButton) diff --git a/Client/Frontend/Browser/Toolbars/UrlBar/ReaderModeButton.swift b/Client/Frontend/Browser/Toolbars/UrlBar/ReaderModeButton.swift index b3c8e27b43c..b6e27e1bed8 100644 --- a/Client/Frontend/Browser/Toolbars/UrlBar/ReaderModeButton.swift +++ b/Client/Frontend/Browser/Toolbars/UrlBar/ReaderModeButton.swift @@ -21,13 +21,13 @@ class ReaderModeButton: UIButton { override var isSelected: Bool { didSet { - self.tintColor = (isHighlighted || isSelected) ? selectedTintColor : unselectedTintColor + updateAppearance() } } override open var isHighlighted: Bool { didSet { - self.tintColor = (isHighlighted || isSelected) ? selectedTintColor : unselectedTintColor + updateAppearance() } } @@ -37,6 +37,10 @@ class ReaderModeButton: UIButton { } } + private func updateAppearance() { + self.tintColor = (isHighlighted || isSelected) ? selectedTintColor : unselectedTintColor + } + private var _readerModeState: ReaderModeState = .unavailable var readerModeState: ReaderModeState { diff --git a/Client/Frontend/Browser/Toolbars/UrlBar/TabLocationView.swift b/Client/Frontend/Browser/Toolbars/UrlBar/TabLocationView.swift index 90e6ce927d5..255185ac5a8 100644 --- a/Client/Frontend/Browser/Toolbars/UrlBar/TabLocationView.swift +++ b/Client/Frontend/Browser/Toolbars/UrlBar/TabLocationView.swift @@ -161,7 +161,7 @@ class TabLocationView: UIView { return readerModeButton }() - lazy var reloadButton = ToolbarButton().then { + lazy var reloadButton = ToolbarButton(top: true).then { $0.accessibilityIdentifier = "TabToolbar.stopReloadButton" $0.accessibilityLabel = Strings.TabToolbarReloadButtonAccessibilityLabel $0.setImage(#imageLiteral(resourceName: "nav-refresh").template, for: .normal) @@ -172,7 +172,7 @@ class TabLocationView: UIView { } lazy var shieldsButton: ToolbarButton = { - let button = ToolbarButton() + let button = ToolbarButton(top: true) button.setImage(UIImage(imageLiteralResourceName: "shields-menu-icon"), for: .normal) button.addTarget(self, action: #selector(didClickBraveShieldsButton), for: .touchUpInside) button.imageView?.contentMode = .center @@ -380,20 +380,21 @@ extension TabLocationView: AccessibilityActionsSource { // MARK: - Themeable extension TabLocationView: Themeable { + var themeableChildren: [Themeable?]? { + return [reloadButton] + } + func applyTheme(_ theme: Theme) { - switch theme { - case .regular: - backgroundColor = BraveUX.LocationBarBackgroundColor - case .private: - backgroundColor = BraveUX.LocationBarBackgroundColor_PrivateMode - } - - urlTextField.textColor = UIColor.Browser.Tint.colorFor(theme) - readerModeButton.selectedTintColor = UIColor.TextField.ReaderModeButtonSelected.colorFor(theme) - readerModeButton.unselectedTintColor = UIColor.TextField.ReaderModeButtonUnselected.colorFor(theme) + styleChildren(theme: theme) - reloadButton.applyTheme(theme) - separatorLine.backgroundColor = UIColor.TextField.Separator.colorFor(theme) + backgroundColor = theme.colors.addressBar.withAlphaComponent(theme.colors.transparencies.addressBarAlpha) + + urlTextField.textColor = theme.colors.tints.addressBar + + readerModeButton.unselectedTintColor = theme.colors.tints.header + readerModeButton.selectedTintColor = theme.colors.accent + + separatorLine.backgroundColor = theme.colors.border.withAlphaComponent(theme.colors.transparencies.borderAlpha) } } diff --git a/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift b/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift index 6a06dfce83a..34562124250 100644 --- a/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift +++ b/Client/Frontend/Browser/Toolbars/UrlBar/TopToolbarView.swift @@ -67,7 +67,7 @@ class TopToolbarView: UIView, ToolbarProtocol { } } - fileprivate var currentTheme: Theme = .regular + fileprivate var currentTheme: Theme? var toolbarIsShowing = false @@ -111,11 +111,7 @@ class TopToolbarView: UIView, ToolbarProtocol { let line = UIView() - lazy var tabsButton: TabsButton = { - let tabsButton = TabsButton.tabTrayButton() - tabsButton.accessibilityIdentifier = "TopToolbarView.tabsButton" - return tabsButton - }() + let tabsButton = TabsButton(top: true) fileprivate lazy var progressBar: GradientProgressBar = { let progressBar = GradientProgressBar() @@ -140,22 +136,22 @@ class TopToolbarView: UIView, ToolbarProtocol { return button }() - lazy var bookmarkButton = ToolbarButton().then { + lazy var bookmarkButton = ToolbarButton(top: true).then { $0.setImage(#imageLiteral(resourceName: "menu_bookmarks").template, for: .normal) $0.accessibilityLabel = Strings.BookmarksMenuItem $0.addTarget(self, action: #selector(didClickBookmarkButton), for: .touchUpInside) } - var forwardButton = ToolbarButton() - var shareButton = ToolbarButton() - var addTabButton = ToolbarButton() - lazy var menuButton = ToolbarButton().then { + var forwardButton = ToolbarButton(top: true) + var shareButton = ToolbarButton(top: true) + var addTabButton = ToolbarButton(top: true) + lazy var menuButton = ToolbarButton(top: true).then { $0.contentMode = .center $0.accessibilityIdentifier = "topToolbarView-menuButton" } var backButton: ToolbarButton = { - let backButton = ToolbarButton() + let backButton = ToolbarButton(top: true) backButton.accessibilityIdentifier = "TopToolbarView.backButton" return backButton }() @@ -283,6 +279,8 @@ class TopToolbarView: UIView, ToolbarProtocol { } } + /// Created whenever the location bar on top is selected + /// it is "converted" from static to actual TextField private func createLocationTextField() { guard locationTextField == nil else { return } @@ -310,7 +308,10 @@ class TopToolbarView: UIView, ToolbarProtocol { make.edges.equalTo(self.locationView).inset(insets) } - locationTextField.applyTheme(currentTheme) + if let theme = currentTheme { + // If no theme exists here, then this will be styled after parent calls `applyTheme` at a later point + locationTextField.applyTheme(theme) + } } override func becomeFirstResponder() -> Bool { @@ -577,23 +578,21 @@ extension TopToolbarView: AutocompleteTextFieldDelegate { // MARK: - Themeable extension TopToolbarView: Themeable { + var themeableChildren: [Themeable?]? { + return [locationView, locationTextField] + actionButtons + } func applyTheme(_ theme: Theme) { - locationView.applyTheme(theme) - locationTextField?.applyTheme(theme) - actionButtons.forEach { $0.applyTheme(theme) } - tabsButton.applyTheme(theme) + styleChildren(theme: theme) - progressBar.setGradientColors(startColor: UIColor.LoadingBar.Start.colorFor(theme), endColor: UIColor.LoadingBar.End.colorFor(theme)) + // Currently do not use gradient, hence same start/end color + progressBar.setGradientColors(startColor: theme.colors.accent, endColor: theme.colors.accent) currentTheme = theme - cancelButton.setTitleColor(UIColor.Browser.Tint.colorFor(theme), for: .normal) - switch theme { - case .regular: - backgroundColor = BraveUX.ToolbarsBackgroundSolidColor - case .private: - backgroundColor = BraveUX.DarkToolbarsBackgroundSolidColor - } - line.backgroundColor = UIColor.Browser.URLBarDivider.colorFor(theme) + cancelButton.setTitleColor(theme.colors.tints.header, for: .normal) + + backgroundColor = theme.colors.header + line.backgroundColor = theme.colors.border + line.alpha = theme.colors.transparencies.borderAlpha } } diff --git a/Client/Frontend/Browser/Toolbars/UrlBar/UrlBarTextField.swift b/Client/Frontend/Browser/Toolbars/UrlBar/UrlBarTextField.swift index bf45074bf6a..4795983f5ba 100644 --- a/Client/Frontend/Browser/Toolbars/UrlBar/UrlBarTextField.swift +++ b/Client/Frontend/Browser/Toolbars/UrlBar/UrlBarTextField.swift @@ -71,15 +71,13 @@ class UrlBarTextField: AutocompleteTextField { extension UrlBarTextField: Themeable { func applyTheme(_ theme: Theme) { - switch theme { - case .regular: - backgroundColor = BraveUX.LocationBarBackgroundColor - case .private: - backgroundColor = BraveUX.LocationBarBackgroundColor_PrivateMode - } + styleChildren(theme: theme) + + backgroundColor = theme.colors.addressBar.withAlphaComponent(theme.colors.transparencies.addressBarAlpha) - textColor = UIColor.TextField.TextAndTint.colorFor(theme) - clearButtonTintColor = textColor - highlightColor = UIColor.TextField.Highlight.colorFor(theme) + let text = theme.colors.tints.header + textColor = text + clearButtonTintColor = text + highlightColor = text.withAlphaComponent(0.2) } } diff --git a/Client/Frontend/Popover/PopoverContainerView.swift b/Client/Frontend/Popover/PopoverContainerView.swift index 5881024430b..bb0218f04ca 100644 --- a/Client/Frontend/Popover/PopoverContainerView.swift +++ b/Client/Frontend/Popover/PopoverContainerView.swift @@ -47,6 +47,14 @@ extension PopoverController { } } + /// Color of menu + var color: UIColor? { + didSet { + contentView.backgroundColor = color + shadowView.backgroundColor = color + } + } + /// The view where you will place the content controller's view let contentView = UIView().then { $0.backgroundColor = PopoverUX.backgroundColor diff --git a/Client/Frontend/Popover/PopoverController.swift b/Client/Frontend/Popover/PopoverController.swift index 2a63c09ccc3..0b5a6df398d 100644 --- a/Client/Frontend/Popover/PopoverController.swift +++ b/Client/Frontend/Popover/PopoverController.swift @@ -69,6 +69,14 @@ class PopoverController: UIViewController { /// Whether or not to automatically dismiss the popup when the device orientation changes var dismissesOnOrientationChanged = true + /// Defines the desired color for the entire popup menu + /// Child controller may specify their own `backgroundColor`, however arrow/carrot color is also handled here + var color: UIColor? { + didSet { + containerView.color = color + } + } + /// Allows the presenter to know when the popover was dismissed by some gestural action. var popoverDidDismiss: ((_ popoverController: PopoverController) -> Void)? diff --git a/Client/Frontend/Settings/ClearPrivateDataTableViewController.swift b/Client/Frontend/Settings/ClearPrivateDataTableViewController.swift index e05e9045fd9..d67ef17fd59 100644 --- a/Client/Frontend/Settings/ClearPrivateDataTableViewController.swift +++ b/Client/Frontend/Settings/ClearPrivateDataTableViewController.swift @@ -67,9 +67,6 @@ class ClearPrivateDataTableViewController: UITableViewController { title = Strings.ClearPrivateData tableView.register(SettingsTableSectionHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: SectionHeaderFooterIdentifier) - - tableView.separatorColor = UIConstants.TableViewSeparatorColor - tableView.backgroundColor = UIConstants.TableViewHeaderBackgroundColor } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -78,7 +75,6 @@ class ClearPrivateDataTableViewController: UITableViewController { if indexPath.section == SectionToggles { cell.textLabel?.text = clearables[indexPath.item].clearable.label let control = UISwitch() - control.onTintColor = UIConstants.ControlTintColor control.addTarget(self, action: #selector(switchValueChanged(_:)), for: .valueChanged) control.isOn = toggles[indexPath.item] cell.accessoryView = control diff --git a/Client/Frontend/Settings/OptionSelectionViewController.swift b/Client/Frontend/Settings/OptionSelectionViewController.swift index 380627e3982..787a92c0a77 100644 --- a/Client/Frontend/Settings/OptionSelectionViewController.swift +++ b/Client/Frontend/Settings/OptionSelectionViewController.swift @@ -93,14 +93,7 @@ class OptionSelectionViewController: TableV required init?(coder aDecoder: NSCoder) { fatalError() } - - override func viewDidLoad() { - super.viewDidLoad() - tableView.separatorColor = UIConstants.TableViewSeparatorColor - tableView.backgroundColor = UIConstants.TableViewHeaderBackgroundColor - } - private func updateRowsForSelectedOption() { for (idx, option) in options.enumerated() { if option.key == selectedOption.key { diff --git a/Client/Frontend/Settings/SearchSettingsTableViewController.swift b/Client/Frontend/Settings/SearchSettingsTableViewController.swift index ee4f197a2bf..e15e30d4620 100644 --- a/Client/Frontend/Settings/SearchSettingsTableViewController.swift +++ b/Client/Frontend/Settings/SearchSettingsTableViewController.swift @@ -46,11 +46,7 @@ class SearchSettingsTableViewController: UITableViewController { } let footer = SettingsTableSectionHeaderFooterView(frame: CGRect(width: tableView.bounds.width, height: 44)) - footer.showBottomBorder = false tableView.tableFooterView = footer - - tableView.separatorColor = SettingsUX.TableViewSeparatorColor - tableView.backgroundColor = SettingsUX.TableViewHeaderBackgroundColor } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -69,7 +65,6 @@ class SearchSettingsTableViewController: UITableViewController { cell = UITableViewCell(style: .default, reuseIdentifier: nil) cell.textLabel?.text = Strings.SearchSettingSuggestionCellTitle let toggle = UISwitch() - toggle.onTintColor = UIConstants.ControlTintColor toggle.addTarget(self, action: #selector(didToggleSearchSuggestions), for: .valueChanged) toggle.isOn = model.shouldShowSearchSuggestions cell.editingAccessoryView = toggle @@ -87,7 +82,6 @@ class SearchSettingsTableViewController: UITableViewController { cell.showsReorderControl = true let toggle = UISwitch() - toggle.onTintColor = UIConstants.ControlTintColor // This is an easy way to get from the toggle control to the corresponding index. toggle.tag = index toggle.addTarget(self, action: #selector(didToggleEngine), for: .valueChanged) diff --git a/Client/Frontend/Settings/SettingsTableSectionHeaderFooterView.swift b/Client/Frontend/Settings/SettingsTableSectionHeaderFooterView.swift index a05dd15ca38..dc15ff09ceb 100644 --- a/Client/Frontend/Settings/SettingsTableSectionHeaderFooterView.swift +++ b/Client/Frontend/Settings/SettingsTableSectionHeaderFooterView.swift @@ -37,44 +37,16 @@ class SettingsTableSectionHeaderFooterView: UITableViewHeaderFooterView { } } - var showTopBorder: Bool = true { - didSet { - topBorder.isHidden = !showTopBorder - } - } - - var showBottomBorder: Bool = true { - didSet { - bottomBorder.isHidden = !showBottomBorder - } - } - lazy var titleLabel: UILabel = { var headerLabel = UILabel() - headerLabel.textColor = SettingsUX.TableViewHeaderTextColor headerLabel.font = UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.regular) headerLabel.numberOfLines = 0 return headerLabel }() - fileprivate lazy var topBorder: UIView = { - let topBorder = UIView() - topBorder.backgroundColor = UIConstants.SeparatorColor - return topBorder - }() - - fileprivate lazy var bottomBorder: UIView = { - let bottomBorder = UIView() - bottomBorder.backgroundColor = UIConstants.SeparatorColor - return bottomBorder - }() - override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) - contentView.backgroundColor = SettingsUX.TableViewHeaderBackgroundColor addSubview(titleLabel) - addSubview(topBorder) - addSubview(bottomBorder) setupInitialConstraints() } @@ -84,23 +56,11 @@ class SettingsTableSectionHeaderFooterView: UITableViewHeaderFooterView { } func setupInitialConstraints() { - bottomBorder.snp.makeConstraints { make in - make.bottom.left.right.equalTo(self) - make.height.equalTo(0.5) - } - - topBorder.snp.makeConstraints { make in - make.top.left.right.equalTo(self) - make.height.equalTo(0.5) - } - remakeTitleAlignmentConstraints() } override func prepareForReuse() { super.prepareForReuse() - showTopBorder = true - showBottomBorder = true titleLabel.text = nil titleAlignment = .bottom } diff --git a/Client/Frontend/Settings/SettingsViewController.swift b/Client/Frontend/Settings/SettingsViewController.swift index bc595889a1f..e884a6827f6 100644 --- a/Client/Frontend/Settings/SettingsViewController.swift +++ b/Client/Frontend/Settings/SettingsViewController.swift @@ -86,8 +86,6 @@ class SettingsViewController: TableViewController { self.tabManager = tabManager super.init(style: .grouped) - - UITableViewCell.appearance().tintColor = BraveUX.BraveOrange } @available(*, unavailable) @@ -96,14 +94,15 @@ class SettingsViewController: TableViewController { } override func viewDidLoad() { - navigationItem.title = Strings.Settings - tableView.accessibilityIdentifier = "SettingsViewController.tableView" - tableView.separatorColor = UIConstants.TableViewSeparatorColor - tableView.backgroundColor = UIConstants.TableViewHeaderBackgroundColor - dataSource.sections = sections + + applyTheme(theme) + } + + private var theme: Theme { + Theme.of(tabManager.selectedTab) } private var sections: [Section] { @@ -142,6 +141,12 @@ class SettingsViewController: TableViewController { ] ) + let reloadCell = { (row: Row, displayString: String) in + if let indexPath = self.dataSource.indexPath(rowUUID: row.uuid, sectionUUID: general.uuid) { + self.dataSource.sections[indexPath.section].rows[indexPath.row].detailText = displayString + } + } + if UIDevice.current.userInterfaceIdiom == .pad { general.rows.append( Row(text: Strings.Show_Tabs_Bar, accessory: .switchToggle(value: Preferences.General.tabBarVisibility.value == TabBarVisibility.always.rawValue, { Preferences.General.tabBarVisibility.value = $0 ? TabBarVisibility.always.rawValue : TabBarVisibility.never.rawValue }), cellClass: MultilineValue1Cell.self) @@ -155,10 +160,7 @@ class SettingsViewController: TableViewController { selectedOption: TabBarVisibility(rawValue: Preferences.General.tabBarVisibility.value), optionChanged: { [unowned self] _, option in Preferences.General.tabBarVisibility.value = option.rawValue - - if let indexPath = self.dataSource.indexPath(rowUUID: row.uuid, sectionUUID: general.uuid) { - self.dataSource.sections[indexPath.section].rows[indexPath.row].detailText = option.displayString - } + reloadCell(row, option.displayString) } ) optionsViewController.headerText = Strings.Show_Tabs_Bar @@ -167,6 +169,24 @@ class SettingsViewController: TableViewController { general.rows.append(row) } + let themeSubtitle = Theme.DefaultTheme(rawValue: Preferences.General.themeNormalMode.value)?.displayString + var row = Row(text: Strings.ThemesDisplayBrightness, detailText: themeSubtitle, accessory: .disclosureIndicator, cellClass: MultilineSubtitleCell.self) + row.selection = { [unowned self] in + let optionsViewController = OptionSelectionViewController( + options: Theme.DefaultTheme.normalThemesOptions, + selectedOption: Theme.DefaultTheme(rawValue: Preferences.General.themeNormalMode.value), + optionChanged: { [unowned self] _, option in + Preferences.General.themeNormalMode.value = option.rawValue + reloadCell(row, option.displayString) + self.applyTheme(self.theme) + } + ) + optionsViewController.headerText = Strings.ThemesDisplayBrightness + optionsViewController.footerText = Strings.ThemesDisplayBrightnessFooter + self.navigationController?.pushViewController(optionsViewController, animated: true) + } + general.rows.append(row) + general.rows.append( BoolRow(title: Strings.Show_Bookmark_Button_In_Top_Toolbar, option: Preferences.General.showBookmarkToolbarShortcut) ) @@ -270,7 +290,24 @@ class SettingsViewController: TableViewController { } }) ] - privacy.rows.append(BoolRow(title: Strings.Private_Browsing_Only, option: Preferences.Privacy.privateBrowsingOnly)) + privacy.rows.append( + BoolRow( + title: Strings.Private_Browsing_Only, + option: Preferences.Privacy.privateBrowsingOnly, + onValueChange: { + Preferences.Privacy.privateBrowsingOnly.value = $0 + + // Need to flush the table, hacky, but works consistenly and well + let superView = self.tableView.superview + self.tableView.removeFromSuperview() + DispatchQueue.main.async { + // Let shield toggle change propagate, otherwise theme may not be set properly + superView?.addSubview(self.tableView) + self.applyTheme(self.theme) + } + } + ) + ) return privacy }() @@ -421,6 +458,18 @@ class SettingsViewController: TableViewController { } } +extension TableViewController: Themeable { + func applyTheme(_ theme: Theme) { + styleChildren(theme: theme) + tableView.reloadData() + + // View manipulations done via `apperance()` do not impact existing UI, so need to adjust manually + // exiting menus, so setting explicitly. + navigationController?.navigationBar.tintColor = UINavigationBar.appearance().tintColor + navigationController?.navigationBar.barTintColor = UINavigationBar.appearance().appearanceBarTintColor + } +} + fileprivate class MultilineButtonCell: ButtonCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { diff --git a/Client/Frontend/Shields/ShieldsView.swift b/Client/Frontend/Shields/ShieldsView.swift index 2e08234fb13..ca2ebb726ba 100644 --- a/Client/Frontend/Shields/ShieldsView.swift +++ b/Client/Frontend/Shields/ShieldsView.swift @@ -8,7 +8,7 @@ import BraveShared extension ShieldsViewController { /// The custom loaded view for the `ShieldsViewController` - class View: UIView { + class View: UIView, Themeable { private let scrollView = UIScrollView() let stackView: UIStackView = { @@ -23,7 +23,6 @@ extension ShieldsViewController { // Global Shields Override let shieldOverrideControl: ToggleView = { let toggleView = ToggleView(title: Strings.Site_shield_settings, toggleSide: .right) - toggleView.titleLabel.textColor = BraveUX.GreyJ toggleView.titleLabel.font = .systemFont(ofSize: 17.0, weight: .medium) return toggleView }() @@ -63,6 +62,19 @@ extension ShieldsViewController { required init?(coder aDecoder: NSCoder) { fatalError() } + + // MARK: - Themeable + func applyTheme(_ theme: Theme) { + styleChildren(theme: theme) + + backgroundColor = theme.colors.home + + // Overview + overviewStackView.overviewFooterLabel.textColor = overviewStackView.overviewLabel.textColor.withAlphaComponent(0.6) + + // Normal shields panel + shieldsContainerStackView.set(theme: theme) + } } class OverviewContainerStackView: UIStackView { @@ -79,7 +91,6 @@ extension ShieldsViewController { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 15.0) - label.textColor = .lightGray label.text = Strings.Shields_Overview_Footer return label }() @@ -103,7 +114,6 @@ extension ShieldsViewController { /// Create a header label private class func headerLabel(title: String) -> UILabel { let label = UILabel() - label.textColor = UIColor(white: 0.4, alpha: 1.0) label.font = .systemFont(ofSize: 15.0) label.text = title return label @@ -128,10 +138,10 @@ extension ShieldsViewController { // Stats let statsHeaderLabel = headerLabel(title: Strings.Blocking_Monitor) - let adsTrackersStatView = StatView(title: Strings.Ads_and_Trackers, valueColor: BraveUX.BraveOrange) - let httpsUpgradesStatView = StatView(title: Strings.HTTPS_Upgrades, valueColor: BraveUX.Green) - let scriptsBlockedStatView = StatView(title: Strings.Scripts_Blocked, valueColor: BraveUX.Purple) - let fingerprintingStatView = StatView(title: Strings.Fingerprinting_Methods, valueColor: BraveUX.GreyG) + let adsTrackersStatView = StatView(title: Strings.Ads_and_Trackers) + let httpsUpgradesStatView = StatView(title: Strings.HTTPS_Upgrades) + let scriptsBlockedStatView = StatView(title: Strings.Scripts_Blocked) + let fingerprintingStatView = StatView(title: Strings.Fingerprinting_Methods) // Settings let settingsDivider = dividerView() @@ -173,6 +183,21 @@ extension ShieldsViewController { required init(coder: NSCoder) { fatalError() } + + func set(theme: Theme) { + let stats = theme.colors.stats + [ + adsTrackersStatView: stats.ads, + httpsUpgradesStatView: stats.httpse, + scriptsBlockedStatView: stats.trackers + ].forEach { + $0.0.valueLabel.textColor = $0.1 + } + + let faddedColor = hostLabel.textColor.withAlphaComponent(0.8) + statsHeaderLabel.textColor = faddedColor + settingsHeaderLabel.textColor = faddedColor + } } /// Displays some UI that displays the block count of a stat. Set `valueLabel.text` to the stat @@ -194,11 +219,9 @@ extension ShieldsViewController { l.numberOfLines = 0 return l }() - /// Create the stat view with a given title and color - init(title: String, valueColor: UIColor) { + /// Create the stat view with a given title + init(title: String) { super.init(frame: .zero) - - valueLabel.textColor = valueColor titleLabel.text = title addSubview(valueLabel) diff --git a/Client/Frontend/Shields/ShieldsViewController.swift b/Client/Frontend/Shields/ShieldsViewController.swift index c6c00439d1c..7a3779c6bf0 100644 --- a/Client/Frontend/Shields/ShieldsViewController.swift +++ b/Client/Frontend/Shields/ShieldsViewController.swift @@ -144,6 +144,7 @@ class ShieldsViewController: UIViewController, PopoverContentComponent { override func loadView() { view = View() + shieldsView.applyTheme(Theme.of(tab)) } override func viewDidLoad() { diff --git a/Client/Frontend/Sync/SyncAddDeviceViewController.swift b/Client/Frontend/Sync/SyncAddDeviceViewController.swift index 2199ff4ea24..e6b6c4bed27 100644 --- a/Client/Frontend/Sync/SyncAddDeviceViewController.swift +++ b/Client/Frontend/Sync/SyncAddDeviceViewController.swift @@ -24,7 +24,6 @@ class SyncAddDeviceViewController: SyncViewController { lazy var codewordsView: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.medium) - label.textColor = BraveUX.GreyJ label.lineBreakMode = NSLineBreakMode.byWordWrapping label.numberOfLines = 0 return label @@ -93,7 +92,6 @@ class SyncAddDeviceViewController: SyncViewController { containerView = UIView() containerView.translatesAutoresizingMaskIntoConstraints = false - containerView.backgroundColor = UIColor.white containerView.layer.cornerRadius = 8 containerView.layer.masksToBounds = true @@ -155,12 +153,10 @@ class SyncAddDeviceViewController: SyncViewController { titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.semibold) - titleLabel.textColor = BraveUX.GreyJ titleDescriptionStackView.addArrangedSubview(titleLabel) descriptionLabel = UILabel() descriptionLabel.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.regular) - descriptionLabel.textColor = BraveUX.GreyH descriptionLabel.numberOfLines = 0 descriptionLabel.lineBreakMode = .byTruncatingTail descriptionLabel.textAlignment = .center diff --git a/Client/Frontend/Sync/SyncCameraView.swift b/Client/Frontend/Sync/SyncCameraView.swift index 2e78e28435b..d654b787745 100644 --- a/Client/Frontend/Sync/SyncCameraView.swift +++ b/Client/Frontend/Sync/SyncCameraView.swift @@ -30,7 +30,6 @@ class SyncCameraView: UIView, AVCaptureMetadataOutputObjectsDelegate { cameraOverlayView = UIImageView(image: UIImage(named: "camera-overlay")?.withRenderingMode(.alwaysTemplate)) cameraOverlayView.contentMode = .center - cameraOverlayView.tintColor = UIColor.white addSubview(cameraOverlayView) addSubview(cameraAccessButton) addSubview(openSettingsButton) diff --git a/Client/Frontend/Sync/SyncSelectDeviceTypeViewController.swift b/Client/Frontend/Sync/SyncSelectDeviceTypeViewController.swift index 1c91e325f2d..8f506b5bd6a 100644 --- a/Client/Frontend/Sync/SyncSelectDeviceTypeViewController.swift +++ b/Client/Frontend/Sync/SyncSelectDeviceTypeViewController.swift @@ -12,16 +12,26 @@ class SyncDeviceTypeButton: UIControl { var imageView: UIImageView = UIImageView() var label: UILabel = UILabel() var type: DeviceType! + + // Color for the opposite state of `pressed` + private var pressedReversedColor = BraveUX.BraveOrange var pressed: Bool = false { didSet { + if pressed == oldValue { + // Needed with usage of `pressedReversedColor` + return + } + + let newColor = pressedReversedColor + pressedReversedColor = label.textColor + label.textColor = newColor + if pressed { - label.textColor = BraveUX.BraveOrange if let anim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY) { anim.toValue = NSValue(cgSize: CGSize(width: 0.9, height: 0.9)) layer.pop_add(anim, forKey: "size") } } else { - label.textColor = BraveUX.GreyJ if let anim = POPSpringAnimation(propertyNamed: kPOPLayerScaleXY) { anim.toValue = NSValue(cgSize: CGSize(width: 1.0, height: 1.0)) layer.pop_add(anim, forKey: "size") @@ -34,7 +44,6 @@ class SyncDeviceTypeButton: UIControl { self.init(frame: CGRect.zero) clipsToBounds = false - backgroundColor = UIColor.white layer.cornerRadius = 12 layer.shadowColor = BraveUX.GreyJ.cgColor layer.shadowRadius = 3 @@ -48,7 +57,6 @@ class SyncDeviceTypeButton: UIControl { label.text = title label.font = UIFont.systemFont(ofSize: 17.0, weight: UIFont.Weight.bold) - label.textColor = BraveUX.GreyJ label.textAlignment = .center addSubview(label) @@ -100,7 +108,6 @@ class SyncSelectDeviceTypeViewController: SyncViewController { $0.textAlignment = .center $0.numberOfLines = 0 $0.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.regular) - $0.textColor = BraveUX.GreyH } let mainStackView = UIStackView().then { diff --git a/Client/Frontend/Sync/SyncViewController.swift b/Client/Frontend/Sync/SyncViewController.swift index 42237ff8e6f..1f36e13b1e1 100644 --- a/Client/Frontend/Sync/SyncViewController.swift +++ b/Client/Frontend/Sync/SyncViewController.swift @@ -5,10 +5,6 @@ import Shared import BraveShared import Data -struct SyncUX { - static let backgroundColor = UIColor(rgb: 0xF8F8F8) -} - class RoundInterfaceButton: UIButton { override func layoutSubviews() { super.layoutSubviews() @@ -18,10 +14,12 @@ class RoundInterfaceButton: UIButton { class SyncViewController: UIViewController { + override func loadView() { + view = SyncView() + } + override func viewDidLoad() { super.viewDidLoad() - - view.backgroundColor = SyncUX.backgroundColor NotificationCenter.default.addObserver(self, selector: #selector(didLeaveSyncGroup), name: Sync.Notifications.didLeaveSyncGroup, object: nil) } @@ -45,4 +43,7 @@ class SyncViewController: UIViewController { self?.navigationController?.popToRootViewController(animated: true) } } + + // This is used for `appearance()` usage, so can target sync background views + class SyncView: UIView {} } diff --git a/Client/Frontend/Sync/SyncWelcomeViewController.swift b/Client/Frontend/Sync/SyncWelcomeViewController.swift index c34cbc28d6b..6c3503f8176 100644 --- a/Client/Frontend/Sync/SyncWelcomeViewController.swift +++ b/Client/Frontend/Sync/SyncWelcomeViewController.swift @@ -43,7 +43,6 @@ class SyncWelcomeViewController: SyncViewController { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.semibold) - label.textColor = BraveUX.GreyJ label.text = Strings.BraveSync label.textAlignment = .center return label @@ -53,7 +52,6 @@ class SyncWelcomeViewController: SyncViewController { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.regular) - label.textColor = BraveUX.GreyH label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping label.textAlignment = .center diff --git a/Client/Frontend/UIConstants.swift b/Client/Frontend/UIConstants.swift index e5985f5ce49..e5020148098 100644 --- a/Client/Frontend/UIConstants.swift +++ b/Client/Frontend/UIConstants.swift @@ -6,29 +6,6 @@ import Foundation import Shared import BraveShared -// A browser color represents the color of UI in both Private browsing mode and normal mode -struct BrowserColor { - let normalColor: UIColor - let PBMColor: UIColor - init(normal: UIColor, pbm: UIColor) { - self.normalColor = normal - self.PBMColor = pbm - } - - init(normal: Int, pbm: Int) { - self.normalColor = UIColor(rgb: normal) - self.PBMColor = UIColor(rgb: pbm) - } - - func color(isPBM: Bool) -> UIColor { - return isPBM ? PBMColor : normalColor - } - - func colorFor(_ theme: Theme) -> UIColor { - return color(isPBM: theme.isPrivate) - } -} - extension UIColor { // These are defaults from http://design.firefox.com/photon/visuals/color.html struct Defaults { @@ -39,53 +16,6 @@ extension UIColor { static let PaleBlue = UIColor(rgb: 0xB0D5FB) static let LightBeige = UIColor(rgb: 0xf0e6dc) } - - struct Browser { - static let Background = BrowserColor(normal: Photon.Grey10, pbm: Photon.Grey70) - static let Text = BrowserColor(normal: .white, pbm: Photon.Grey60) - static let URLBarDivider = BrowserColor(normal: Photon.Grey90A10, pbm: Photon.Grey60) - static let LocationBarBackground = Photon.Grey30 - static let Tint = BrowserColor(normal: Photon.Grey80, pbm: Photon.Grey30) - } - - struct URLBar { - static let Border = BrowserColor(normal: Photon.Grey50, pbm: Photon.Grey80) - static let ActiveBorder = BrowserColor(normal: Photon.Blue50A30, pbm: Photon.Grey60) - static let Tint = BrowserColor(normal: Photon.Blue50A30, pbm: Photon.Grey10) - } - - struct TextField { - static let Background = BrowserColor(normal: .white, pbm: Defaults.MobileGreyF) - static let TextAndTint = BrowserColor(normal: Photon.Grey80, pbm: .white) - static let Highlight = BrowserColor(normal: Defaults.iOSHighlightBlue, pbm: Defaults.Purple60A30) - static let ReaderModeButtonSelected = BrowserColor(normal: Photon.Blue40, pbm: Defaults.MobilePrivatePurple) - static let ReaderModeButtonUnselected = BrowserColor(normal: Photon.Grey50, pbm: Photon.Grey40) - static let PageOptionsSelected = ReaderModeButtonSelected - static let PageOptionsUnselected = UIColor.Browser.Tint - static let Separator = BrowserColor(normal: #colorLiteral(red: 0.7333333333, green: 0.7333333333, blue: 0.8039215686, alpha: 1), pbm: Photon.Grey70) - - } - - // The back/forward/refresh/menu button (bottom toolbar) - struct ToolbarButton { - static let SelectedTint = BrowserColor(normal: Photon.Blue40, pbm: Photon.Purple50) - static let DisabledTint = BrowserColor(normal: Photon.Grey30, pbm: Photon.Grey50) - } - - struct LoadingBar { - static let Start = BrowserColor(normal: Photon.Blue50A30, pbm: Photon.Purple50) - static let End = BrowserColor(normal: Photon.Blue50, pbm: Photon.Magenta50) - } - - struct TabTray { - static let Background = Browser.Background - static let ToolbarButtonTint = BrowserColor(normal: Photon.Grey80, pbm: Photon.Grey30) - } - - struct TopTabs { - static let PrivateModeTint = BrowserColor(normal: Photon.Grey10, pbm: Photon.Grey40) - static let Background = BrowserColor(normal: Photon.White100, pbm: Photon.Grey80) - } } public struct UIConstants { diff --git a/Client/Frontend/Widgets/TabsButton.swift b/Client/Frontend/Widgets/TabsButton.swift index ba7bc5e62ad..9fdd29cd5d6 100644 --- a/Client/Frontend/Widgets/TabsButton.swift +++ b/Client/Frontend/Widgets/TabsButton.swift @@ -7,8 +7,6 @@ import SnapKit import Shared private struct TabsButtonUX { - static let TitleColor: UIColor = UIColor.Photon.Grey80 - static let TitleBackgroundColor: UIColor = UIColor.Photon.White100 static let CornerRadius: CGFloat = 2 static let TitleFont: UIFont = UIConstants.DefaultChromeSmallFontBold static let BorderStrokeWidth: CGFloat = 1.5 @@ -18,33 +16,27 @@ class TabsButton: UIButton { var textColor = UIColor.Photon.White100 { didSet { - countLabel.textColor = textColor - borderView.color = textColor + updateButtonVisuals() } } - var titleBackgroundColor = UIColor.Photon.White100 { - didSet { - labelBackground.backgroundColor = titleBackgroundColor - } - } - var highlightTextColor: UIColor? - var highlightBackgroundColor: UIColor? + + // Explicit, should crash if not setup properly + var highlightTextColor: UIColor! private var currentCount: Int? + private var top: Bool override var isHighlighted: Bool { didSet { - if isHighlighted { - countLabel.textColor = textColor - borderView.color = titleBackgroundColor - labelBackground.backgroundColor = titleBackgroundColor - } else { - countLabel.textColor = textColor - borderView.color = textColor - labelBackground.backgroundColor = titleBackgroundColor - } + updateButtonVisuals() } } + + private func updateButtonVisuals() { + let foregroundColor: UIColor = isHighlighted ? highlightTextColor : textColor + countLabel.textColor = foregroundColor + borderView.color = foregroundColor + } lazy var countLabel: UILabel = { let label = UILabel() @@ -66,6 +58,7 @@ class TabsButton: UIButton { let background = UIView() background.layer.cornerRadius = TabsButtonUX.CornerRadius background.isUserInteractionEnabled = false + background.backgroundColor = .clear return background }() @@ -76,28 +69,27 @@ class TabsButton: UIButton { border.isUserInteractionEnabled = false return border }() - - override init(frame: CGRect) { - super.init(frame: frame) - insideButton.addSubview(labelBackground) - insideButton.addSubview(borderView) - insideButton.addSubview(countLabel) + + required init(top: Bool) { + self.top = top + super.init(frame: .zero) + [labelBackground, borderView, countLabel].forEach(insideButton.addSubview) addSubview(insideButton) isAccessibilityElement = true - accessibilityTraits.insert(.button) + accessibilityTraits.insert(.button) self.accessibilityLabel = Strings.Show_Tabs } + + override init(frame: CGRect) { + fatalError("init(coder:) has not been implemented") + } override func updateConstraints() { super.updateConstraints() - labelBackground.snp.remakeConstraints { (make) -> Void in - make.edges.equalTo(insideButton) - } - borderView.snp.remakeConstraints { (make) -> Void in - make.edges.equalTo(insideButton) - } - countLabel.snp.remakeConstraints { (make) -> Void in - make.edges.equalTo(insideButton) + [labelBackground, borderView, countLabel].forEach { + $0.snp.remakeConstraints { make in + make.edges.equalTo(insideButton) + } } insideButton.snp.remakeConstraints { (make) -> Void in make.size.equalTo(19) @@ -122,11 +114,10 @@ class TabsButton: UIButton { extension TabsButton: Themeable { func applyTheme(_ theme: Theme) { - titleBackgroundColor = UIColor.Browser.Background.colorFor(theme) - textColor = UIColor.Browser.Tint.colorFor(theme) - countLabel.textColor = UIColor.Browser.Tint.colorFor(theme) - borderView.color = UIColor.Browser.Tint.colorFor(theme) - labelBackground.backgroundColor = UIColor.Browser.Background.colorFor(theme) + styleChildren(theme: theme) + + textColor = top ? theme.colors.tints.header : theme.colors.tints.footer + highlightTextColor = theme.colors.accent } } diff --git a/Client/Frontend/Widgets/Theme.swift b/Client/Frontend/Widgets/Theme.swift deleted file mode 100644 index 3bc06f7b8a4..00000000000 --- a/Client/Frontend/Widgets/Theme.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation - -protocol Themeable { - - func applyTheme(_ theme: Theme) - -} - -enum Theme: String { - - /// Regular browsing. - case regular - - /// Private browsing. - case `private` - - /// Textual representation suitable for debugging. - var debugDescription: String { - switch self { - case .regular: - return "Regular theme" - case .private: - return "Private theme" - } - } - - /// Returns whether the theme is private or not. - var isPrivate: Bool { - switch self { - case .regular: - return false - case .private: - return true - } - } - - /// Returns the theme of the given Tab, if the tab is nil returns a regular theme. - /// - /// - parameter tab: An object representing a Tab. - /// - returns: A Tab theme. - static func of(_ tab: Tab?) -> Theme { - if let tab = tab { - switch TabType.of(tab) { - case .regular: - return .regular - case .private: - return .private - } - } - return PrivateBrowsingManager.shared.isPrivateBrowsing ? .private : .regular - } - -} diff --git a/Client/Frontend/Widgets/Themes/Theme.swift b/Client/Frontend/Widgets/Themes/Theme.swift new file mode 100644 index 00000000000..164183af514 --- /dev/null +++ b/Client/Frontend/Widgets/Themes/Theme.swift @@ -0,0 +1,331 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import Foundation +import BraveShared +import Shared + +protocol Themeable { + + var themeableChildren: [Themeable?]? { get } + + // This method should _always_ call `styleChildren`, regardless of `themableChildren` value. + func applyTheme(_ theme: Theme) +} + +extension Themeable { + var themeableChildren: [Themeable?]? { return nil } + + func applyTheme(_ theme: Theme) { + styleChildren(theme: theme) + } + + func styleChildren(theme: Theme) { + self.themeableChildren?.forEach { $0?.applyTheme(theme) } + } +} + +class Theme: Equatable, Decodable { + + enum DefaultTheme: String, RepresentableOptionType { + case system = "Z71ED37E-EC3E-436E-AD5F-B22748306A6B" + case light = "ACE618A3-D6FC-45A4-94F2-1793C40AE927" + case dark = "B900A41F-2C02-4664-9DE4-C170956339AC" + case `private` = "C5CB0D9A-5467-432C-AB35-1A78C55CFB41" + + var theme: Theme { + return Theme.from(id: self.rawValue) + } + + static var normalThemesOptions: [DefaultTheme] { + if #available(iOS 13.0, *) { + return [DefaultTheme.system, DefaultTheme.light, DefaultTheme.dark] + } else { + // iOS 12 .system is treated as .light + return [DefaultTheme.system, DefaultTheme.dark] + } + } + + public var displayString: String { + // Due to translations needs, titles are hardcoded here, ideally they would be pulled from the + // theme files themselves. + + if #available(iOS 13.0, *) { + // continue + } else if self == .system { + // iOS 12 .system is treated as .light + return Strings.ThemesLightOption + } + + switch self { + case .system: return Strings.ThemesAutomaticOption + case .light: return Strings.ThemesLightOption + case .dark: return Strings.ThemesDarkOption + + // Should not be visible, but making explicit so compiler will capture any `DefaultTheme` modifications + case .private: return "" + } + } + } + + fileprivate static let ThemeDirectory = Bundle.main.resourceURL!.appendingPathComponent("Themes") + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ThemeCodingKeys.self) + uuid = try container.decode(String.self, forKey: .uuid) + title = try container.decode(String.self, forKey: .title) + url = try container.decode(URL.self, forKey: .url) + description = try container.decode(String.self, forKey: .description) + thumbnail = try container.decode(URL.self, forKey: .thumbnail) + isDark = try container.decode(Bool.self, forKey: .isDark) + enabled = try container.decode(Bool.self, forKey: .enabled) + + colors = try container.decode(Color.self, forKey: .colors) + images = try container.decode(Image.self, forKey: .images) + } + + let uuid: String + let title: String + let url: URL + let description: String + let thumbnail: URL + let isDark: Bool + let enabled: Bool + + let colors: Color + struct Color: Decodable { + + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: ThemeCodingKeys.ColorCodingKeys.self) + let headerStr = try container.decode(String.self, forKey: .header) + let footerStr = try container.decode(String.self, forKey: .footer) + let homeStr = try container.decode(String.self, forKey: .home) + let addressBarStr = try container.decode(String.self, forKey: .addressBar) + let borderStr = try container.decode(String.self, forKey: .border) + let accentStr = try container.decode(String.self, forKey: .accent) + + header = UIColor(colorString: headerStr) + footer = UIColor(colorString: footerStr) + home = UIColor(colorString: homeStr) + addressBar = UIColor(colorString: addressBarStr) + border = UIColor(colorString: borderStr) + accent = UIColor(colorString: accentStr) + + stats = try container.decode(Stat.self, forKey: .stats) + tints = try container.decode(Tint.self, forKey: .tints) + transparencies = try container.decode(Transparency.self, forKey: .transparencies) + } + + let header: UIColor + let footer: UIColor + let home: UIColor + let addressBar: UIColor + let border: UIColor + let accent: UIColor + + let stats: Stat + struct Stat: Decodable { + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: ThemeCodingKeys.ColorCodingKeys.StatCodingKeys.self) + let adsStr = try container.decode(String.self, forKey: .ads) + let trackersStr = try container.decode(String.self, forKey: .trackers) + let httpseStr = try container.decode(String.self, forKey: .httpse) + let timeSavedStr = try container.decode(String.self, forKey: .timeSaved) + + ads = UIColor(colorString: adsStr) + trackers = UIColor(colorString: trackersStr) + httpse = UIColor(colorString: httpseStr) + timeSaved = UIColor(colorString: timeSavedStr) + } + + let ads: UIColor + let trackers: UIColor + let httpse: UIColor + let timeSaved: UIColor + } + + let tints: Tint + struct Tint: Decodable { + init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: ThemeCodingKeys.ColorCodingKeys.TintCodingKeys.self) + let homeStr = try container.decode(String.self, forKey: .home) + let headerStr = try container.decode(String.self, forKey: .header) + let footerStr = try container.decode(String.self, forKey: .footer) + let addressBarStr = try container.decode(String.self, forKey: .addressBar) + + home = UIColor(colorString: homeStr) + header = UIColor(colorString: headerStr) + footer = UIColor(colorString: footerStr) + addressBar = UIColor(colorString: addressBarStr) + } + + let home: UIColor + let header: UIColor + let footer: UIColor + let addressBar: UIColor + } + + let transparencies: Transparency + struct Transparency: Decodable { + let addressBarAlpha: CGFloat + let borderAlpha: CGFloat + } + } + + let images: Image + struct Image: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ThemeCodingKeys.ImageCodingKeys.self) + header = try container.decode(URL.self, forKey: .header) + footer = try container.decode(URL.self, forKey: .footer) + home = try container.decode(URL.self, forKey: .home) + } + + let header: URL + let footer: URL + let home: URL + } + + /// Textual representation suitable for debugging. + var debugDescription: String { + return description + } + + /// Returns the theme of the given Tab, if the tab is nil returns a regular theme. + /// + /// - parameter tab: An object representing a Tab. + /// - returns: A Tab theme. + static func of(_ tab: Tab?) -> Theme { + guard let tab = tab else { + // This is important, used when switching modes when no tab is present + // TODO: Theme: unit test + let themeBasedOnMode = PrivateBrowsingManager.shared.isPrivateBrowsing + ? Preferences.General.themePrivateMode + : Preferences.General.themeNormalMode + return Theme.from(id: themeBasedOnMode.value) + } + + let themeType = { () -> Preferences.Option in + switch TabType.of(tab) { + case .regular: + return Preferences.General.themeNormalMode + case .private: + return Preferences.General.themePrivateMode + } + }() + + let chosenTheme = DefaultTheme(rawValue: themeType.value) + return chosenTheme?.theme ?? DefaultTheme.system.theme + } + + static var themeMemoryBank: [String: Theme] = [:] + static func from(id: String) -> Theme { + var id = id + if id == DefaultTheme.system.rawValue { + id = currentSystemThemeId + } + + if let inMemoryTheme = themeMemoryBank[id] { + return inMemoryTheme + } + + let themePath = Theme.ThemeDirectory.appendingPathComponent(id).appendingPathExtension("json").path + guard + let themeData = FileManager.default.contents(atPath: themePath), + let theme = try? JSONDecoder().decode(Theme.self, from: themeData) else { + // TODO: Theme: Maybe throw error, but fallback to `system` / default / light + fatalError("Theme file not found for: \(id)... no good") + } + + themeMemoryBank[id] = theme + return theme + } + + private static var currentSystemThemeId: String { + // Should really be based off of preferences, a dark theme and light theme preference + + let fallback = DefaultTheme.light.rawValue + if #available(iOS 13.0, *) { + let isDark = UITraitCollection.current.userInterfaceStyle == .dark + return isDark ? DefaultTheme.dark.rawValue : fallback + } + return fallback + } + + static let allThemes: [Theme] = { + do { + let filenames = try FileManager.default.contentsOfDirectory(at: Theme.ThemeDirectory, includingPropertiesForKeys: []) + + let final = filenames.filter { + $0.pathExtension == "json" + }.compactMap { fullPath -> Theme? in + var path = fullPath.lastPathComponent + path.removeLast(5) // Removing JSON extension + return Theme.from(id: path) + }.filter { + $0.enabled + } + + return final + } catch { + fatalError("`Themes` directory is not available!") + } + }() + + static func == (lhs: Theme, rhs: Theme) -> Bool { + return lhs.uuid == rhs.uuid + } +} + +fileprivate enum ThemeCodingKeys: String, CodingKey { + case uuid + case title + case url + case description + case thumbnail + case isDark + case enabled + + case colors + enum ColorCodingKeys: String, CodingKey { + case header + case footer + case home + case addressBar + case border + case accent + + case tints + enum TintCodingKeys: String, CodingKey { + case home + case header + case footer + case addressBar + } + + case transparencies + enum TransparencyCodingKeys: String, CodingKey { + case addressBarAlpha + case borderAlpha + } + + case stats + enum StatCodingKeys: String, CodingKey { + case ads + case trackers + case httpse + case timeSaved + } + } + + case images + enum ImageCodingKeys: String, CodingKey { + case header + case footer + case home + } +}