Skip to content

Commit

Permalink
Refactor Today widget to MVVM architecture : viewModel and model file…
Browse files Browse the repository at this point in the history
…s added (mozilla-mobile#6864)
  • Loading branch information
noorhashem authored and dnarcese committed Jul 14, 2020
1 parent 2176a27 commit 74091a2
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 28 deletions.
35 changes: 35 additions & 0 deletions Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1169,6 +1181,12 @@
/* Begin PBXFileReference section */
03CCC9171AF05E7300DBF30D /* RelativeDatesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelativeDatesTests.swift; sourceTree = "<group>"; };
0430A544203B372D00FDF76D /* IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
048B4C7824A53E7B001B56E8 /* TodayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayModel.swift; sourceTree = "<group>"; };
048B4C7C24A53EA8001B56E8 /* TodayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewModel.swift; sourceTree = "<group>"; };
048B4C7F24A53EEB001B56E8 /* ImageButtonWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageButtonWithLabel.swift; sourceTree = "<group>"; };
048B4C8224A53F26001B56E8 /* ButtonWithSublabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithSublabel.swift; sourceTree = "<group>"; };
048B4C8524A53F3E001B56E8 /* TodayUX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayUX.swift; sourceTree = "<group>"; };
048B4C8824A53F81001B56E8 /* UIButtonExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButtonExtensions.swift; sourceTree = "<group>"; };
0B21E8051E26CCB7000C8779 /* EarlGrey.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EarlGrey.framework; path = Carthage/Build/iOS/EarlGrey.framework; sourceTree = "<group>"; };
0B305E1A1E3A98A900BE0767 /* BookmarkingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkingTests.swift; sourceTree = "<group>"; };
0B3D670D1E09B90B00C1EFC7 /* AuthenticationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 = "<group>";
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand All @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
56 changes: 56 additions & 0 deletions Extensions/Today/ButtonWithSublabel.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
47 changes: 47 additions & 0 deletions Extensions/Today/ImageButtonWithLabel.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
17 changes: 17 additions & 0 deletions Extensions/Today/TodayModel.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
30 changes: 30 additions & 0 deletions Extensions/Today/TodayUX.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
51 changes: 23 additions & 28 deletions Extensions/Today/TodayViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
}()

Expand All @@ -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

Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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)")
}
Expand Down
Loading

0 comments on commit 74091a2

Please sign in to comment.