/* 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 UIKit import NotificationCenter import Shared import SnapKit import XCGLogger private let log = Logger.browserLogger struct TodayStrings { static let NewPrivateTabButtonLabel = NSLocalizedString("TodayWidget.NewPrivateTabButtonLabel", tableName: "Today", value: "New Private Tab", comment: "New Private Tab button label") static let NewTabButtonLabel = NSLocalizedString("TodayWidget.NewTabButtonLabel", tableName: "Today", value: "New Tab", comment: "New Tab button label") static let GoToCopiedLinkLabel = NSLocalizedString("TodayWidget.GoToCopiedLinkLabel", tableName: "Today", value: "Go to copied link", comment: "Go to link on clipboard") } struct TodayUX { static let privateBrowsingColor = UIColor(rgb: 0xCE6EFC) static let backgroundHightlightColor = UIColor(white: 216.0/255.0, alpha: 44.0/255.0) static let linkTextSize: CGFloat = 10.0 static let labelTextSize: CGFloat = 14.0 static let imageButtonTextSize: CGFloat = 14.0 static let copyLinkImageWidth: CGFloat = 23 static let margin: CGFloat = 8 static let buttonsHorizontalMarginPercentage: CGFloat = 0.1 static let iOS9LeftMargin: CGFloat = 40 } @objc (TodayViewController) class TodayViewController: UIViewController, NCWidgetProviding { var copiedURL: URL? fileprivate lazy var newTabButton: ImageButtonWithLabel = { let imageButton = ImageButtonWithLabel() imageButton.addTarget(self, action: #selector(onPressNewTab), forControlEvents: .touchUpInside) imageButton.label.text = TodayStrings.NewTabButtonLabel let button = imageButton.button button.setImage(UIImage(named: "new_tab_button_normal")?.withRenderingMode(.alwaysTemplate), for: .normal) button.setImage(UIImage(named: "new_tab_button_highlight")?.withRenderingMode(.alwaysTemplate), for: .highlighted) let label = imageButton.label label.font = UIFont.systemFont(ofSize: TodayUX.imageButtonTextSize) imageButton.sizeToFit() return imageButton }() fileprivate lazy var newPrivateTabButton: ImageButtonWithLabel = { let imageButton = ImageButtonWithLabel() imageButton.addTarget(self, action: #selector(onPressNewPrivateTab), forControlEvents: .touchUpInside) imageButton.label.text = TodayStrings.NewPrivateTabButtonLabel let button = imageButton.button button.setImage(UIImage(named: "new_private_tab_button_normal"), for: .normal) button.setImage(UIImage(named: "new_private_tab_button_highlight"), for: .highlighted) let label = imageButton.label label.tintColor = TodayUX.privateBrowsingColor label.textColor = TodayUX.privateBrowsingColor label.font = UIFont.systemFont(ofSize: TodayUX.imageButtonTextSize) imageButton.sizeToFit() return imageButton }() fileprivate lazy var openCopiedLinkButton: ButtonWithSublabel = { let button = ButtonWithSublabel() button.setTitle(TodayStrings.GoToCopiedLinkLabel, for: .normal) button.addTarget(self, action: #selector(onPressOpenClibpoard), for: .touchUpInside) // We need to set the background image/color for .Normal, so the whole button is tappable. button.setBackgroundColor(UIColor.clear, forState: .normal) button.setBackgroundColor(TodayUX.backgroundHightlightColor, forState: .highlighted) button.setImage(UIImage(named: "copy_link_icon")?.withRenderingMode(.alwaysTemplate), for: .normal) button.label.font = UIFont.systemFont(ofSize: TodayUX.labelTextSize) button.subtitleLabel.font = UIFont.systemFont(ofSize: TodayUX.linkTextSize) return button }() fileprivate lazy var widgetStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical stackView.alignment = .fill stackView.spacing = TodayUX.margin / 2 stackView.distribution = UIStackViewDistribution.fill stackView.layoutMargins = UIEdgeInsets(top: TodayUX.margin, left: TodayUX.margin, bottom: TodayUX.margin, right: TodayUX.margin) stackView.isLayoutMarginsRelativeArrangement = true return stackView }() fileprivate lazy var buttonStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal stackView.alignment = .fill stackView.spacing = 0 stackView.distribution = UIStackViewDistribution.fillEqually let edge = self.view.frame.size.width * TodayUX.buttonsHorizontalMarginPercentage stackView.layoutMargins = UIEdgeInsets(top: 0, left: edge, bottom: 0, right: edge) stackView.isLayoutMarginsRelativeArrangement = true return stackView }() fileprivate var scheme: String { guard let string = Bundle.main.object(forInfoDictionaryKey: "MozInternalURLScheme") as? String else { // Something went wrong/weird, but we should fallback to the public one. return "firefox" } return string } override func viewDidLoad() { super.viewDidLoad() let widgetView: UIView! self.extensionContext?.widgetLargestAvailableDisplayMode = .compact let effectView = UIVisualEffectView(effect: UIVibrancyEffect.widgetPrimary()) self.view.addSubview(effectView) effectView.snp.makeConstraints { make in make.edges.equalTo(self.view) } widgetView = effectView.contentView buttonStackView.addArrangedSubview(newTabButton) buttonStackView.addArrangedSubview(newPrivateTabButton) widgetStackView.addArrangedSubview(buttonStackView) widgetStackView.addArrangedSubview(openCopiedLinkButton) widgetView.addSubview(widgetStackView) widgetStackView.snp.makeConstraints { make in make.edges.equalTo(widgetView) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateCopiedLink() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { let edge = size.width * TodayUX.buttonsHorizontalMarginPercentage buttonStackView.layoutMargins = UIEdgeInsets(top: 0, left: edge, bottom: 0, right: edge) } func widgetMarginInsets(forProposedMarginInsets defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets { return UIEdgeInsets.zero } func updateCopiedLink() { UIPasteboard.general.asyncURL().uponQueue(.main) { res in if let copiedURL: URL? = res.successValue, let url = copiedURL { self.openCopiedLinkButton.isHidden = false self.openCopiedLinkButton.subtitleLabel.isHidden = SystemUtils.isDeviceLocked() self.openCopiedLinkButton.subtitleLabel.text = url.absoluteDisplayString self.copiedURL = url } else { self.openCopiedLinkButton.isHidden = true self.copiedURL = nil } } } // MARK: Button behaviour @objc func onPressNewTab(_ view: UIView) { openContainingApp() } @objc func onPressNewPrivateTab(_ view: UIView) { openContainingApp("?private=true") } fileprivate func openContainingApp(_ urlSuffix: String = "") { let urlString = "\(scheme)://open-url\(urlSuffix)" self.extensionContext?.open(URL(string: urlString)!) { success in log.info("Extension opened containing app: \(success)") } } @objc func onPressOpenClibpoard(_ view: UIView) { if let url = copiedURL, let encodedString = url.absoluteString.escape() { openContainingApp("?url=\(encodedString)") } } } extension UIButton { func setBackgroundColor(_ color: UIColor, forState state: UIControlState) { let colorView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1)) colorView.backgroundColor = color UIGraphicsBeginImageContext(colorView.bounds.size) if let context = UIGraphicsGetCurrentContext() { colorView.layer.render(in: context) } let colorImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() self.setBackgroundImage(colorImage, for: state) } } class ImageButtonWithLabel: UIView { lazy var button = UIButton() lazy var label = UILabel() required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override init(frame: CGRect) { super.init(frame: frame) performLayout() } func performLayout() { addSubview(button) addSubview(label) button.snp.makeConstraints { make in make.top.left.centerX.equalTo(self) } label.snp.makeConstraints { make in make.top.equalTo(button.snp.bottom) make.leading.trailing.bottom.equalTo(self) } label.numberOfLines = 1 label.lineBreakMode = .byWordWrapping label.textAlignment = .center label.textColor = UIColor.white } func addTarget(_ target: AnyObject?, action: Selector, forControlEvents events: UIControlEvents) { button.addTarget(target, action: action, for: events) } } class ButtonWithSublabel: UIButton { lazy var subtitleLabel: UILabel = UILabel() lazy var label: UILabel = UILabel() required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } convenience init() { self.init(frame: CGRect.zero) } override init(frame: CGRect) { super.init(frame: frame) performLayout() } fileprivate func performLayout() { let titleLabel = self.label self.titleLabel?.removeFromSuperview() addSubview(titleLabel) let imageView = self.imageView! let subtitleLabel = self.subtitleLabel subtitleLabel.textColor = UIColor.lightGray self.addSubview(subtitleLabel) imageView.snp.makeConstraints { make in make.centerY.left.equalTo(self) make.width.equalTo(TodayUX.copyLinkImageWidth) } titleLabel.snp.makeConstraints { make in make.left.equalTo(imageView.snp.right).offset(TodayUX.margin) make.trailing.top.equalTo(self) } subtitleLabel.lineBreakMode = .byTruncatingTail subtitleLabel.snp.makeConstraints { make in make.bottom.equalTo(self) make.top.equalTo(titleLabel.snp.bottom) make.leading.trailing.equalTo(titleLabel) } } override func setTitle(_ text: String?, for state: UIControlState) { self.label.text = text super.setTitle(text, for: state) } }