diff --git a/App/Browser View/BrowserViewController.swift b/App/Browser View/BrowserViewController.swift index 39da99d..a239ebe 100644 --- a/App/Browser View/BrowserViewController.swift +++ b/App/Browser View/BrowserViewController.swift @@ -32,6 +32,7 @@ class BrowserViewController: UIViewController private var loadingObservation: NSKeyValueObservation? private var backButtonObservation: NSKeyValueObservation? private var forwardButtonObservation: NSKeyValueObservation? + private var hasSecureContentObservation: NSKeyValueObservation? private var activeTabObservation: AnyCancellable? private var faviconObservation: AnyCancellable? @@ -454,15 +455,21 @@ class BrowserViewController: UIViewController // Back/forward observer toolbarController.backButton.isEnabled = webView.canGoBack - backButtonObservation = webView.observe(\.canGoBack, changeHandler: { [toolbarController] (webView, observedChange) in + backButtonObservation = webView.observe(\.canGoBack, changeHandler: { [unowned self] (webView, observedChange) in toolbarController.backButton.isEnabled = webView.canGoBack }) toolbarController.forwardButton.isEnabled = webView.canGoForward - forwardButtonObservation = webView.observe(\.canGoForward, changeHandler: { [toolbarController] (webView, observedChange) in + forwardButtonObservation = webView.observe(\.canGoForward, changeHandler: { [unowned self] (webView, observedChange) in toolbarController.forwardButton.isEnabled = webView.canGoForward }) + // Secure content + browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent + hasSecureContentObservation = webView.observe(\.hasOnlySecureContent, changeHandler: { [unowned self] (webView, observedChange) in + browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent + }) + // Favicon observation faviconObservation = tab.$favicon.receive(on: DispatchQueue.main) .sink { [unowned self] _ in diff --git a/App/Titlebar and URL Bar/TitlebarView.swift b/App/Titlebar and URL Bar/TitlebarView.swift index 3f1d36e..96a0c10 100644 --- a/App/Titlebar and URL Bar/TitlebarView.swift +++ b/App/Titlebar and URL Bar/TitlebarView.swift @@ -8,8 +8,51 @@ import UIKit import QuartzCore_Private +class SecurityIndicatorView: UIView +{ + let imageView = UIImageView(image: UIImage(systemName: "lock.fill")) + let label = UILabel(frame: .zero) + + var labelVisible: Bool = true { didSet { setNeedsLayout() } } + + private let imageViewPadding = CGFloat(3.0) + + convenience init() { + self.init(frame: .zero) + + addSubview(imageView) + addSubview(label) + + label.text = "Secure Connection" + imageView.contentMode = .scaleAspectFit + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + var size = CGSize(width: imageView.intrinsicContentSize.width, height: size.height) + if labelVisible { + size.width += label.sizeThatFits(size).width + imageViewPadding + } + + return size + } + + override func layoutSubviews() { + super.layoutSubviews() + + imageView.frame = CGRect(x: 0, y: 0, width: bounds.height, height: bounds.height) + if labelVisible { + label.isHidden = false + label.frame = CGRect(x: imageView.frame.maxX + imageViewPadding, y: 1.0, width: bounds.width - (imageView.frame.maxX + imageViewPadding), height: bounds.height) + } else { + label.isHidden = true + } + } +} + class TitlebarView: UIView { + public var showsSecurityIndicator: Bool = false { didSet { setNeedsLayout() } } + private let titleLabelView = UILabel(frame: .zero) private let backgroundView = GradientView(direction: .horizontal, colors: [ UIColor(red: 0.101, green: 0.176, blue: 0.415, alpha: 1.0), @@ -17,11 +60,13 @@ class TitlebarView: UIView ]) private let separatorView = UIView(frame: .zero) + private let securityIndicatorView = SecurityIndicatorView() convenience init() { self.init(frame: .zero) addSubview(backgroundView) addSubview(titleLabelView) + addSubview(securityIndicatorView) addSubview(separatorView) separatorView.backgroundColor = UIColor(white: 1.0, alpha: 0.20) @@ -33,6 +78,11 @@ class TitlebarView: UIView titleLabelView.layer.shadowOffset = CGSize(width: 0.0, height: 2.0) titleLabelView.font = UIFont.boldSystemFont(ofSize: 12.0) + let securityIndicatorLabelColor = UIColor(white: 1.0, alpha: 0.7) + securityIndicatorView.imageView.tintColor = securityIndicatorLabelColor + securityIndicatorView.label.textColor = securityIndicatorLabelColor + securityIndicatorView.label.font = UIFont.systemFont(ofSize: 10.0) + backgroundView.alpha = 0.98 } @@ -66,8 +116,44 @@ class TitlebarView: UIView override func layoutSubviews() { super.layoutSubviews() + let edgePadding: CGFloat = 8.0 + + // Thought it would be cool to have a different style for regular, but eh. + let securityLabelOnTrailingSide = false // (traitCollection.horizontalSizeClass == .regular) + securityIndicatorView.labelVisible = securityLabelOnTrailingSide + + var securityIndicatorSize = showsSecurityIndicator ? securityIndicatorView.sizeThatFits(bounds.size) : .zero + securityIndicatorSize.height = 10.0 + backgroundView.frame = bounds - titleLabelView.frame = bounds.avoiding(verticalInsets: safeAreaInsets).insetBy(dx: 8.0 + layoutMargins.left, dy: 0.0) + titleLabelView.frame = bounds + .avoiding(verticalInsets: safeAreaInsets) + .insetBy(dx: edgePadding + layoutMargins.left, dy: 0.0) + .subtracting(width: securityIndicatorSize.width, height: 0) + + if showsSecurityIndicator { + securityIndicatorView.isHidden = false + + if !securityLabelOnTrailingSide { + securityIndicatorView.frame = CGRect( + origin: CGPoint(x: edgePadding + layoutMargins.left, y: 0.0), + size: CGSize(width: securityIndicatorSize.height, height: securityIndicatorSize.height) + ) + + // Scooch the title over a bit + titleLabelView.frame.origin.x = securityIndicatorView.frame.maxX + 4.0 + } else { + securityIndicatorView.frame = CGRect( + x: bounds.width - layoutMargins.right - securityIndicatorSize.width, y: 0.0, + width: securityIndicatorSize.width, + height: securityIndicatorSize.height + ) + } + + securityIndicatorView.frame = securityIndicatorView.frame.centeredY(inRect: titleLabelView.frame) + } else { + securityIndicatorView.isHidden = true + } let separatorHeight = 1.0 / UIScreen.main.scale separatorView.frame = CGRect(x: 0, y: bounds.height - separatorHeight, width: bounds.width, height: separatorHeight) diff --git a/App/Utilities/Geometry.swift b/App/Utilities/Geometry.swift index 504e1b7..ef9036c 100644 --- a/App/Utilities/Geometry.swift +++ b/App/Utilities/Geometry.swift @@ -23,16 +23,24 @@ extension CGRect return rect } + public func subtracting(width: CGFloat, height: CGFloat) -> CGRect { + var rect = self + rect.size.width -= width + rect.size.height -= height + + return rect + } + public func centeredY(inRect: CGRect) -> CGRect { var rect = self - rect.origin.y = CGRound((inRect.height - rect.height) / 2.0) + rect.origin.y = CGRound(inRect.origin.y + (inRect.height - rect.height) / 2.0) return rect } public func centeredX(inRect: CGRect) -> CGRect { var rect = self - rect.origin.x = CGRound((inRect.width - rect.width) / 2.0) + rect.origin.x = CGRound(inRect.origin.x + (inRect.width - rect.width) / 2.0) return rect }