From 74091a2325113783a9882c810ff7a48682c0dba2 Mon Sep 17 00:00:00 2001 From: Noor Hashem Date: Tue, 7 Jul 2020 22:24:23 +0300 Subject: [PATCH] Refactor Today widget to MVVM architecture : viewModel and model files added (#6864) --- Client.xcodeproj/project.pbxproj | 35 +++++++++++++ Extensions/Today/ButtonWithSublabel.swift | 56 +++++++++++++++++++++ Extensions/Today/ImageButtonWithLabel.swift | 47 +++++++++++++++++ Extensions/Today/TodayModel.swift | 17 +++++++ Extensions/Today/TodayUX.swift | 30 +++++++++++ Extensions/Today/TodayViewController.swift | 51 +++++++++---------- Extensions/Today/TodayViewModel.swift | 29 +++++++++++ Extensions/Today/UIButtonExtensions.swift | 21 ++++++++ 8 files changed, 258 insertions(+), 28 deletions(-) create mode 100644 Extensions/Today/ButtonWithSublabel.swift create mode 100644 Extensions/Today/ImageButtonWithLabel.swift create mode 100644 Extensions/Today/TodayModel.swift create mode 100644 Extensions/Today/TodayUX.swift create mode 100644 Extensions/Today/TodayViewModel.swift create mode 100644 Extensions/Today/UIButtonExtensions.swift diff --git a/Client.xcodeproj/project.pbxproj b/Client.xcodeproj/project.pbxproj index 1ef1e7bebd457..2d471133fae8f 100644 --- a/Client.xcodeproj/project.pbxproj +++ b/Client.xcodeproj/project.pbxproj @@ -9,6 +9,18 @@ /* Begin PBXBuildFile section */ 03CCC9181AF05E7300DBF30D /* RelativeDatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CCC9171AF05E7300DBF30D /* RelativeDatesTests.swift */; }; 0430A545203B372D00FDF76D /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0430A544203B372D00FDF76D /* IntegrationTests.swift */; }; + 048B4C7924A53E7B001B56E8 /* TodayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C7824A53E7B001B56E8 /* TodayModel.swift */; }; + 048B4C7A24A53E7B001B56E8 /* TodayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C7824A53E7B001B56E8 /* TodayModel.swift */; }; + 048B4C7D24A53EA8001B56E8 /* TodayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C7C24A53EA8001B56E8 /* TodayViewModel.swift */; }; + 048B4C7E24A53EA8001B56E8 /* TodayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C7C24A53EA8001B56E8 /* TodayViewModel.swift */; }; + 048B4C8024A53EEB001B56E8 /* ImageButtonWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C7F24A53EEB001B56E8 /* ImageButtonWithLabel.swift */; }; + 048B4C8124A53EEB001B56E8 /* ImageButtonWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C7F24A53EEB001B56E8 /* ImageButtonWithLabel.swift */; }; + 048B4C8324A53F26001B56E8 /* ButtonWithSublabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C8224A53F26001B56E8 /* ButtonWithSublabel.swift */; }; + 048B4C8424A53F26001B56E8 /* ButtonWithSublabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C8224A53F26001B56E8 /* ButtonWithSublabel.swift */; }; + 048B4C8624A53F3E001B56E8 /* TodayUX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C8524A53F3E001B56E8 /* TodayUX.swift */; }; + 048B4C8724A53F3E001B56E8 /* TodayUX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C8524A53F3E001B56E8 /* TodayUX.swift */; }; + 048B4C8924A53F81001B56E8 /* UIButtonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C8824A53F81001B56E8 /* UIButtonExtensions.swift */; }; + 048B4C8A24A53F81001B56E8 /* UIButtonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B4C8824A53F81001B56E8 /* UIButtonExtensions.swift */; }; 0B21E8061E26CCB7000C8779 /* EarlGrey.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0B21E8051E26CCB7000C8779 /* EarlGrey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 0B305E1B1E3A98A900BE0767 /* BookmarkingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B305E1A1E3A98A900BE0767 /* BookmarkingTests.swift */; }; 0B3D670E1E09B90B00C1EFC7 /* AuthenticationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B3D670D1E09B90B00C1EFC7 /* AuthenticationTest.swift */; }; @@ -1169,6 +1181,12 @@ /* Begin PBXFileReference section */ 03CCC9171AF05E7300DBF30D /* RelativeDatesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelativeDatesTests.swift; sourceTree = ""; }; 0430A544203B372D00FDF76D /* IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = ""; }; + 048B4C7824A53E7B001B56E8 /* TodayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayModel.swift; sourceTree = ""; }; + 048B4C7C24A53EA8001B56E8 /* TodayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewModel.swift; sourceTree = ""; }; + 048B4C7F24A53EEB001B56E8 /* ImageButtonWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageButtonWithLabel.swift; sourceTree = ""; }; + 048B4C8224A53F26001B56E8 /* ButtonWithSublabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithSublabel.swift; sourceTree = ""; }; + 048B4C8524A53F3E001B56E8 /* TodayUX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayUX.swift; sourceTree = ""; }; + 048B4C8824A53F81001B56E8 /* UIButtonExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButtonExtensions.swift; sourceTree = ""; }; 0B21E8051E26CCB7000C8779 /* EarlGrey.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EarlGrey.framework; path = Carthage/Build/iOS/EarlGrey.framework; sourceTree = ""; }; 0B305E1A1E3A98A900BE0767 /* BookmarkingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkingTests.swift; sourceTree = ""; }; 0B3D670D1E09B90B00C1EFC7 /* AuthenticationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationTest.swift; sourceTree = ""; }; @@ -2477,6 +2495,12 @@ 391AEFD11C8F11ED00691F84 /* Images.xcassets */, 390527531C874D35007E0BB7 /* Info.plist */, 3905274E1C874D35007E0BB7 /* TodayViewController.swift */, + 048B4C7824A53E7B001B56E8 /* TodayModel.swift */, + 048B4C7C24A53EA8001B56E8 /* TodayViewModel.swift */, + 048B4C7F24A53EEB001B56E8 /* ImageButtonWithLabel.swift */, + 048B4C8224A53F26001B56E8 /* ButtonWithSublabel.swift */, + 048B4C8524A53F3E001B56E8 /* TodayUX.swift */, + 048B4C8824A53F81001B56E8 /* UIButtonExtensions.swift */, ); path = Today; sourceTree = ""; @@ -4917,8 +4941,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 048B4C7E24A53EA8001B56E8 /* TodayViewModel.swift in Sources */, + 048B4C8724A53F3E001B56E8 /* TodayUX.swift in Sources */, + 048B4C8124A53EEB001B56E8 /* ImageButtonWithLabel.swift in Sources */, 315D05561E58DD60001F349B /* UIPasteboardExtensions.swift in Sources */, 3905274F1C874D35007E0BB7 /* TodayViewController.swift in Sources */, + 048B4C8A24A53F81001B56E8 /* UIButtonExtensions.swift in Sources */, + 048B4C7A24A53E7B001B56E8 /* TodayModel.swift in Sources */, + 048B4C8424A53F26001B56E8 /* ButtonWithSublabel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5102,6 +5132,7 @@ D0FCF7F51FE45842004A7995 /* UserScriptManager.swift in Sources */, 436715892488216A006D1D4E /* TabTableViewCell.swift in Sources */, E4A960061ABB9C450069AD6F /* ReaderModeUtils.swift in Sources */, + 048B4C8324A53F26001B56E8 /* ButtonWithSublabel.swift in Sources */, 435D660323D793DF0046EFA2 /* UpdateModel.swift in Sources */, EBF47E701F7979DF00899189 /* UnifiedTelemetry.swift in Sources */, E68F36981EA694000048CF44 /* PanelDataObservers.swift in Sources */, @@ -5133,6 +5164,7 @@ 39F819C61FD70F5D009E31E4 /* TabEventHandlers.swift in Sources */, E65607611C08B4E200534B02 /* SearchInputView.swift in Sources */, FA6B2AC21D41F02D00429414 /* Punycode.swift in Sources */, + 048B4C7D24A53EA8001B56E8 /* TodayViewModel.swift in Sources */, 43446CEA2412066500F5C643 /* UIViewControllerExtension.swift in Sources */, D301AAEE1A3A55B70078DD1D /* TabTrayControllerV1.swift in Sources */, EB9A179B20E69A7F00B12184 /* ThemeManager.swift in Sources */, @@ -5141,6 +5173,7 @@ EBB89509219398E500EB91A0 /* TabContentBlocker+ContentScript.swift in Sources */, D3BE7B461B054F8600641031 /* TestAppDelegate.swift in Sources */, 3BB50E111D6274CD004B33DF /* FirefoxHomeTopSitesCell.swift in Sources */, + 048B4C8624A53F3E001B56E8 /* TodayUX.swift in Sources */, 0B62EFD21AD63CD100ACB9CD /* Clearables.swift in Sources */, 7482205C1DBAB56300EEEA72 /* MailProviders.swift in Sources */, C40046FA1CF8E0B200B08303 /* BackForwardListAnimator.swift in Sources */, @@ -5152,6 +5185,7 @@ 274A36CC239EB99400A21587 /* LibraryPanelContextMenu.swift in Sources */, D314E7F71A37B98700426A76 /* TabToolbar.swift in Sources */, CEFA977E1FAA6B490016F365 /* SyncContentSettingsViewController.swift in Sources */, + 048B4C7924A53E7B001B56E8 /* TodayModel.swift in Sources */, E60D03181D511398002FE3F6 /* SyncStatusResolver.swift in Sources */, 435D660723D7962C0046EFA2 /* UpdateCoverSheetTableViewCell.swift in Sources */, C4E3983D1D21F1E7004E89BA /* TopTabsViews.swift in Sources */, @@ -5288,6 +5322,7 @@ A93067E81D0FE18E00C49C6E /* NightModeHelper.swift in Sources */, 3B39EDCB1E16E1AA00EF029F /* CustomSearchViewController.swift in Sources */, E65075571E37F714006961AC /* FaviconFetcher.swift in Sources */, + 048B4C8024A53EEB001B56E8 /* ImageButtonWithLabel.swift in Sources */, D863C8F21F68BFC20058D95F /* GradientProgressBar.swift in Sources */, EB9A178E20E525DF00B12184 /* ThemeSettingsController.swift in Sources */, D3C744CD1A687D6C004CE85D /* URIFixup.swift in Sources */, diff --git a/Extensions/Today/ButtonWithSublabel.swift b/Extensions/Today/ButtonWithSublabel.swift new file mode 100644 index 0000000000000..a3d559d46873e --- /dev/null +++ b/Extensions/Today/ButtonWithSublabel.swift @@ -0,0 +1,56 @@ +/* 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 + +class ButtonWithSublabel: UIButton { + lazy var subtitleLabel = UILabel() + lazy var label = UILabel() + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + convenience init() { + self.init(frame: .zero) + } + + override init(frame: CGRect) { + super.init(frame: frame) + performLayout() + } + + fileprivate func performLayout() { + let buttonImage = self.imageView! + self.titleLabel?.removeFromSuperview() + addSubview(self.label) + addSubview(self.subtitleLabel) + + buttonImage.snp.makeConstraints { make in + make.centerY.left.equalTo(10) + make.width.equalTo(TodayUX.copyLinkImageWidth) + } + + self.label.snp.makeConstraints { make in + make.left.equalTo(buttonImage.snp.right).offset(10) + make.trailing.top.equalTo(self) + make.height.greaterThanOrEqualTo(15) + } + self.label.numberOfLines = 1 + self.label.lineBreakMode = .byWordWrapping + + self.subtitleLabel.lineBreakMode = .byTruncatingTail + self.subtitleLabel.snp.makeConstraints { make in + make.bottom.equalTo(self).inset(10) + make.top.equalTo(self.label.snp.bottom).offset(3) + make.leading.trailing.equalTo(self.label) + make.height.greaterThanOrEqualTo(10) + } + } + + override func setTitle(_ text: String?, for state: UIControl.State) { + self.label.text = text + super.setTitle(text, for: state) + } +} diff --git a/Extensions/Today/ImageButtonWithLabel.swift b/Extensions/Today/ImageButtonWithLabel.swift new file mode 100644 index 0000000000000..5cfe20fd93fe0 --- /dev/null +++ b/Extensions/Today/ImageButtonWithLabel.swift @@ -0,0 +1,47 @@ +/* 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 + +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() + } + + fileprivate func performLayout() { + addSubview(button) + addSubview(label) + button.imageView?.contentMode = .scaleAspectFit + + button.snp.makeConstraints { make in + make.centerX.equalTo(self) + make.top.equalTo(self.safeAreaLayoutGuide).offset(5) + make.right.greaterThanOrEqualTo(self.safeAreaLayoutGuide).offset(40) + make.left.greaterThanOrEqualTo(self.safeAreaLayoutGuide).inset(40) + make.height.greaterThanOrEqualTo(60) + } + + label.snp.makeConstraints { make in + make.top.equalTo(button.snp.bottom).offset(10) + make.leading.trailing.bottom.equalTo(self) + make.height.greaterThanOrEqualTo(10) + } + + label.numberOfLines = 1 + label.lineBreakMode = .byWordWrapping + label.textAlignment = .center + } + + func addTarget(_ target: AnyObject?, action: Selector, forControlEvents events: UIControl.Event) { + button.addTarget(target, action: action, for: events) + } +} diff --git a/Extensions/Today/TodayModel.swift b/Extensions/Today/TodayModel.swift new file mode 100644 index 0000000000000..46fc04c899864 --- /dev/null +++ b/Extensions/Today/TodayModel.swift @@ -0,0 +1,17 @@ +/* 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 + +struct TodayModel { + static var copiedURL: URL? + + 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 + } +} diff --git a/Extensions/Today/TodayUX.swift b/Extensions/Today/TodayUX.swift new file mode 100644 index 0000000000000..43bfe67a9d864 --- /dev/null +++ b/Extensions/Today/TodayUX.swift @@ -0,0 +1,30 @@ +/* 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 + +struct TodayUX { + static let backgroundHightlightColor = UIColor(white: 216.0/255.0, alpha: 44.0/255.0) + static let linkTextSize: CGFloat = 9.0 + static let labelTextSize: CGFloat = 12.0 + static let imageButtonTextSize: CGFloat = 13.0 + static let copyLinkImageWidth: CGFloat = 20 + static let margin: CGFloat = 8 + static let buttonsHorizontalMarginPercentage: CGFloat = 0.1 + static let buttonStackViewSpacing: CGFloat = 20.0 + static var labelColor: UIColor { + if #available(iOS 13, *) { + return UIColor(named: "widgetLabelColors") ?? UIColor(rgb: 0x242327) + } else { + return UIColor(rgb: 0x242327) + } + } + static var subtitleLabelColor: UIColor { + if #available(iOS 13, *) { + return UIColor(named: "subtitleLableColor") ?? UIColor(rgb: 0x38383C) + } else { + return UIColor(rgb: 0x38383C) + } + } +} diff --git a/Extensions/Today/TodayViewController.swift b/Extensions/Today/TodayViewController.swift index 47d8648b025ea..1a307b8c8f37d 100644 --- a/Extensions/Today/TodayViewController.swift +++ b/Extensions/Today/TodayViewController.swift @@ -27,13 +27,15 @@ private struct TodayUX { } @objc (TodayViewController) -class TodayViewController: UIViewController, NCWidgetProviding { - var copiedURL: URL? +class TodayViewController: UIViewController, NCWidgetProviding, TodayWidgetAppearanceDelegate { + + let viewModel = TodayWidgetViewModel() + let model = TodayModel() fileprivate lazy var newTabButton: ImageButtonWithLabel = { let imageButton = ImageButtonWithLabel() imageButton.addTarget(self, action: #selector(onPressNewTab), forControlEvents: .touchUpInside) - imageButton.label.text = TodayStrings.NewTabButtonLabel + imageButton.label.text = String.NewTabButtonLabel let button = imageButton.button button.setImage(UIImage(named: "new_tab_button_normal")?.withRenderingMode(.alwaysTemplate), for: .normal) @@ -71,7 +73,7 @@ class TodayViewController: UIViewController, NCWidgetProviding { fileprivate lazy var openCopiedLinkButton: ButtonWithSublabel = { let button = ButtonWithSublabel() - button.setTitle(TodayStrings.GoToCopiedLinkLabel, for: .normal) + button.setTitle(String.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) @@ -81,8 +83,12 @@ class TodayViewController: UIViewController, NCWidgetProviding { button.subtitleLabel.font = UIFont.systemFont(ofSize: TodayUX.linkTextSize) button.label.textColor = TodayUX.labelColor button.label.tintColor = TodayUX.labelColor + button.label.sizeToFit() button.subtitleLabel.textColor = TodayUX.subtitleLabelColor button.subtitleLabel.tintColor = TodayUX.subtitleLabelColor + button.subtitleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.preferredFont(forTextStyle: .body).withSize(TodayUX.linkTextSize)) + button.label.accessibilityLabel = String.CopiedLinkLabelFromPasteBoard + button.accessibilityTraits = .none return button }() @@ -104,16 +110,9 @@ class TodayViewController: UIViewController, NCWidgetProviding { 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() + viewModel.setViewDelegate(todayViewDelegate: self) let widgetView: UIView! self.extensionContext?.widgetLargestAvailableDisplayMode = .compact @@ -144,7 +143,7 @@ class TodayViewController: UIViewController, NCWidgetProviding { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - updateCopiedLink() + viewModel.updateCopiedLink() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -156,19 +155,15 @@ class TodayViewController: UIViewController, NCWidgetProviding { return .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 - } + func updateCopiedLinkInView(clipboardURL: URL?) { + guard let url = clipboardURL else { + self.openCopiedLinkButton.isHidden = true + self.openCopiedLinkButton.subtitleLabel.isHidden = SystemUtils.isDeviceLocked() + return } + self.openCopiedLinkButton.isHidden = false + self.openCopiedLinkButton.subtitleLabel.isHidden = SystemUtils.isDeviceLocked() + self.openCopiedLinkButton.subtitleLabel.text = url.absoluteDisplayString } // MARK: Button behaviour @@ -179,16 +174,16 @@ class TodayViewController: UIViewController, NCWidgetProviding { @objc func onPressNewPrivateTab(_ view: UIView) { openContainingApp("?private=true") } - + //TODO: Move it to Viewmodel fileprivate func openContainingApp(_ urlSuffix: String = "") { - let urlString = "\(scheme)://open-url\(urlSuffix)" + let urlString = "\(model.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, + if let url = TodayModel.copiedURL, let encodedString = url.absoluteString.escape() { openContainingApp("?url=\(encodedString)") } diff --git a/Extensions/Today/TodayViewModel.swift b/Extensions/Today/TodayViewModel.swift new file mode 100644 index 0000000000000..02a121548c825 --- /dev/null +++ b/Extensions/Today/TodayViewModel.swift @@ -0,0 +1,29 @@ +/* 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 NotificationCenter +protocol TodayWidgetAppearanceDelegate { + func updateCopiedLinkInView(clipboardURL: URL?) +} + +class TodayWidgetViewModel { + var AppearanceDelegate: TodayWidgetAppearanceDelegate? + + func setViewDelegate(todayViewDelegate: TodayWidgetAppearanceDelegate?) { + self.AppearanceDelegate = todayViewDelegate + } + + func updateCopiedLink() { + UIPasteboard.general.asyncURL().uponQueue(.main) { res in + guard let url: URL? = res.successValue else { + TodayModel.copiedURL = nil + self.AppearanceDelegate?.updateCopiedLinkInView(clipboardURL: nil) + return + } + TodayModel.copiedURL = url + self.AppearanceDelegate?.updateCopiedLinkInView(clipboardURL: url) + } + } +} diff --git a/Extensions/Today/UIButtonExtensions.swift b/Extensions/Today/UIButtonExtensions.swift new file mode 100644 index 0000000000000..a008b3a059eab --- /dev/null +++ b/Extensions/Today/UIButtonExtensions.swift @@ -0,0 +1,21 @@ +/* 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 + +extension UIButton { + func setBackgroundColor(_ color: UIColor, forState state: UIControl.State) { + let colorView = UIView(frame: CGRect(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) + } +}