From 220fc6f07076098a52c5afe84c0eec060b81f87b Mon Sep 17 00:00:00 2001 From: James Magahern Date: Wed, 29 Jul 2020 18:17:22 -0700 Subject: [PATCH] Dark mode implemented --- SBrowser.xcodeproj/project.pbxproj | 12 ++++ .../Browser View/BrowserViewController.swift | 6 ++ .../Browser View/ToolbarViewController.swift | 15 ++++- SBrowser/Browser View/URLBar.swift | 40 +++++++++++- SBrowser/Resources/darkmode.css | 65 +++++++++++++++++++ .../SBRProcessBundleBridge.h | 1 + .../SBRProcessBundleBridge.m | 27 +++++++- 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 SBrowser/Resources/darkmode.css diff --git a/SBrowser.xcodeproj/project.pbxproj b/SBrowser.xcodeproj/project.pbxproj index c867b04..b7840b3 100644 --- a/SBrowser.xcodeproj/project.pbxproj +++ b/SBrowser.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1A14FC2324D203D9009B3F83 /* TitlebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A14FC2224D203D9009B3F83 /* TitlebarView.swift */; }; + 1A14FC2624D251BD009B3F83 /* darkmode.css in Resources */ = {isa = PBXBuildFile; fileRef = 1A14FC2524D251BD009B3F83 /* darkmode.css */; }; 1ADFF46024C7DE53006DC7AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF45F24C7DE53006DC7AE /* AppDelegate.swift */; }; 1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF46124C7DE53006DC7AE /* SceneDelegate.swift */; }; 1ADFF46924C7DE54006DC7AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ADFF46824C7DE54006DC7AE /* Assets.xcassets */; }; @@ -54,6 +55,7 @@ /* Begin PBXFileReference section */ 1A14FC2224D203D9009B3F83 /* TitlebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitlebarView.swift; sourceTree = ""; }; + 1A14FC2524D251BD009B3F83 /* darkmode.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = darkmode.css; sourceTree = ""; }; 1ADFF45C24C7DE53006DC7AE /* SBrowser.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SBrowser.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1ADFF45F24C7DE53006DC7AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 1ADFF46124C7DE53006DC7AE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -102,6 +104,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1A14FC2424D2517A009B3F83 /* Resources */ = { + isa = PBXGroup; + children = ( + 1A14FC2524D251BD009B3F83 /* darkmode.css */, + ); + path = Resources; + sourceTree = ""; + }; 1ADFF45324C7DE53006DC7AE = { isa = PBXGroup; children = ( @@ -131,6 +141,7 @@ 1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */, 1ADFF4C124CA6AE4006DC7AE /* Utilities */, 1ADFF4AF24C92E2F006DC7AE /* Web Process Bundle Bridge */, + 1A14FC2424D2517A009B3F83 /* Resources */, 1ADFF47624C7DF7F006DC7AE /* Supporting Files */, ); path = SBrowser; @@ -297,6 +308,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1A14FC2624D251BD009B3F83 /* darkmode.css in Resources */, 1ADFF46C24C7DE54006DC7AE /* LaunchScreen.storyboard in Resources */, 1ADFF46924C7DE54006DC7AE /* Assets.xcassets in Resources */, ); diff --git a/SBrowser/Browser View/BrowserViewController.swift b/SBrowser/Browser View/BrowserViewController.swift index a27d566..70cdd97 100644 --- a/SBrowser/Browser View/BrowserViewController.swift +++ b/SBrowser/Browser View/BrowserViewController.swift @@ -65,6 +65,12 @@ class BrowserViewController: UIViewController, self.present(navController, animated: true, completion: nil) }), for: .touchUpInside) + // Dark mode button + toolbarController.darkModeButton.addAction(UIAction(handler: { _ in + self.bridge.darkModeEnabled = !self.bridge.darkModeEnabled + self.toolbarController.darkModeEnabled = self.bridge.darkModeEnabled + }), for: .touchUpInside) + // TextField delegate toolbarController.urlBar.textField.delegate = self diff --git a/SBrowser/Browser View/ToolbarViewController.swift b/SBrowser/Browser View/ToolbarViewController.swift index 37e3543..8846497 100644 --- a/SBrowser/Browser View/ToolbarViewController.swift +++ b/SBrowser/Browser View/ToolbarViewController.swift @@ -116,12 +116,25 @@ class ToolbarViewController: UIViewController let shareButton = UIButton(frame: .zero) let darkModeButton = UIButton(frame: .zero) + var darkModeEnabled: Bool = false { + didSet { + if darkModeEnabled { + darkModeButton.setImage(darkModeEnabledImage, for: .normal) + } else { + darkModeButton.setImage(darkModeDisabledImage, for: .normal) + } + } + } + + private let darkModeDisabledImage = UIImage(systemName: "moon.circle") + private let darkModeEnabledImage = UIImage(systemName: "moon.circle.fill") + init() { super.init(nibName: nil, bundle: nil) toolbarView.urlBar = urlBar - darkModeButton.setImage(UIImage(systemName: "moon.circle"), for: .normal) + darkModeButton.setImage(darkModeDisabledImage, for: .normal) toolbarView.buttonsView.addButtonView(darkModeButton) shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal) diff --git a/SBrowser/Browser View/URLBar.swift b/SBrowser/Browser View/URLBar.swift index 8da12dd..25a13ba 100644 --- a/SBrowser/Browser View/URLBar.swift +++ b/SBrowser/Browser View/URLBar.swift @@ -23,17 +23,20 @@ class URLBar: UIView private let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) private let progressIndicatorView = ProgressIndicatorView() + private let fadeMaskView = UIImageView(frame: .zero) private let refreshImage = UIImage(systemName: "arrow.clockwise") private let stopImage = UIImage(systemName: "xmark") + private let backgroundCornerRadius: CGFloat = 8 + convenience init() { self.init(frame: .zero) backgroundColor = .clear backgroundView.layer.masksToBounds = true - backgroundView.layer.cornerRadius = 8 + backgroundView.layer.cornerRadius = backgroundCornerRadius backgroundView.layer.borderWidth = 1 backgroundView.layer.borderColor = UIColor.systemFill.cgColor backgroundView.isUserInteractionEnabled = false @@ -85,12 +88,47 @@ class URLBar: UIView return CGSize(width: 1000.0, height: preferredHeight) } + private func fadeBackgroundImageForSize(_ size: CGSize) -> UIImage? { + var image: UIImage? = nil + + UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0)) + if let context = UIGraphicsGetCurrentContext() { + let gradientColorsArray = [ + UIColor(white: 1.0, alpha: 1.0).cgColor, + UIColor(white: 1.0, alpha: 1.0).cgColor, + UIColor(white: 1.0, alpha: 0.08).cgColor, + UIColor(white: 1.0, alpha: 0.08).cgColor + ] + + let locations: [CGFloat] = [ + 0.0, 0.80, 0.90, 1.0 + ] + + if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: locations) { + context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) + } + + image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext(); + } + + return image + } + override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = bounds progressIndicatorView.frame = backgroundView.contentView.bounds textField.frame = bounds.insetBy(dx: 6.0, dy: 0) + fadeMaskView.frame = textField.bounds + fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size) + if !textField.isFirstResponder { + textField.mask = fadeMaskView + } else { + textField.mask = nil + } + let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height) refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize) } diff --git a/SBrowser/Resources/darkmode.css b/SBrowser/Resources/darkmode.css new file mode 100644 index 0000000..6ca101e --- /dev/null +++ b/SBrowser/Resources/darkmode.css @@ -0,0 +1,65 @@ +html, body { + color: #555 !important; + background: #ececec !important; +} + +html, iframe { + filter: invert(100%) !important; + -webkit-filter: invert(100%) !important; +} + +em, +img, +svg, +form, +image, +video, +audio, +embed, +object, +button, +canvas, +figure:empty { + opacity: 0.85; + filter: invert(100%) !important; + -webkit-filter: invert(100%) !important; +} + +form em, +form img, +form svg, +form image, +form video, +form embed, +form object, +form button, +form canvas, +form figure:empty { + filter: invert(0) !important; + -webkit-filter: invert(0) !important; +} + +[style*='background:url']:not(html):not(body):not(input), +[style*='background: url']:not(html):not(body):not(input), +[style*='background-image']:not(html):not(body):not(input) { + opacity: 0.8; + filter: invert(100%) !important; + -webkit-filter: invert(100%) !important; +} + +::-moz-scrollbar {background: #28292a !important} +::-webkit-scrollbar {background: #28292a !important} +::-moz-scrollbar-track {background: #343637 !important} +::-webkit-scrollbar-track {background: #343637 !important} + +::-webkit-scrollbar-thumb { + background: #4d4e4f !important; + border-left: 1px solid #343637 !important; + border-right: 1px solid #343637 !important; +} + +::-moz-scrollbar-thumb { + background: #4d4e4f !important; + border-left: 1px solid #343637 !important; + border-right: 1px solid #343637 !important; +} diff --git a/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.h b/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.h index 27371f3..ba65b10 100644 --- a/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.h +++ b/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.h @@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id delegate; @property (nonatomic, strong) id policyDataSource; @property (nonatomic, assign) BOOL allowAllScripts; // default is NO +@property (nonatomic, assign) BOOL darkModeEnabled; - (void)policyDataSourceDidChange; diff --git a/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.m b/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.m index 191961a..c04ec4c 100644 --- a/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.m +++ b/SBrowser/Web Process Bundle Bridge/SBRProcessBundleBridge.m @@ -13,18 +13,23 @@ #import #import #import +#import #import #import #import +#import @interface SBRProcessBundleBridge () @end @implementation SBRProcessBundleBridge { - WKWebView *_webView; + WKWebView *_webView; + WKWebViewConfiguration *_webViewConfiguration; id _webProcessProxy; + + _WKUserStyleSheet *_darkModeStyleSheet; } - (WKWebView *)webView @@ -58,6 +63,7 @@ [[webView _remoteObjectRegistry] registerExportedObject:self interface:delegateInterface]; _webView = webView; + _webViewConfiguration = configuration; } return _webView; @@ -90,4 +96,23 @@ [_webProcessProxy setAllScriptsAllowed:allowAllScripts]; } +- (void)setDarkModeEnabled:(BOOL)darkModeEnabled +{ + _darkModeEnabled = darkModeEnabled; + + WKUserContentController *userContentController = [_webViewConfiguration userContentController]; + + if (darkModeEnabled) { + if (!_darkModeStyleSheet) { + NSURL *styleSheetURL = [[NSBundle mainBundle] URLForResource:@"darkmode" withExtension:@"css"]; + NSString *styleSheetSource = [NSString stringWithContentsOfURL:styleSheetURL encoding:NSUTF8StringEncoding error:nil]; + _darkModeStyleSheet = [[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]; + } + + [userContentController _addUserStyleSheet:_darkModeStyleSheet]; + } else if (_darkModeStyleSheet) { + [userContentController _removeUserStyleSheet:_darkModeStyleSheet]; + } +} + @end