Dactyloidae/mobile/ios/Extensions/Today/TodayViewController.swift
2026-06-26 21:04:09 -07:00

298 lines
11 KiB
Swift

/* 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)
}
}