Dactyloidae iOS initial commit

This commit is contained in:
wuggy 2026-06-26 21:04:09 -07:00
commit 7154a0497e
2123 changed files with 197052 additions and 0 deletions

View file

@ -0,0 +1,79 @@
/* 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 Shared
import Storage
import SnapKit
/// The ActionViewController is the initial viewcontroller that is presented (full screen) when the share extension
/// is activated. Depending on whether the user is logged in or not, this viewcontroller will present either the
/// InstructionsVC or the ClientPicker VC.
@objc(ActionViewController)
class ActionViewController: UIViewController, ClientPickerViewControllerDelegate, InstructionsViewControllerDelegate {
private var sharedItem: ShareItem?
override func viewDidLoad() {
view.backgroundColor = UIColor.white
super.viewDidLoad()
if !hasAccount() {
let instructionsViewController = InstructionsViewController()
instructionsViewController.delegate = self
let navigationController = UINavigationController(rootViewController: instructionsViewController)
present(navigationController, animated: false, completion: nil)
return
}
ExtensionUtils.extractSharedItemFromExtensionContext(self.extensionContext, completionHandler: { (item, error) -> Void in
guard let item = item, error == nil, item.isShareable else {
let alert = UIAlertController(title: Strings.SendToErrorTitle, message: Strings.SendToErrorMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Strings.SendToErrorOKButton, style: .default) { _ in self.finish() })
self.present(alert, animated: true, completion: nil)
return
}
self.sharedItem = item
let clientPickerViewController = ClientPickerViewController()
clientPickerViewController.clientPickerDelegate = self
clientPickerViewController.profile = nil // This means the picker will open and close the default profile
let navigationController = UINavigationController(rootViewController: clientPickerViewController)
self.present(navigationController, animated: false, completion: nil)
})
}
func finish() {
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
}
func clientPickerViewController(_ clientPickerViewController: ClientPickerViewController, didPickClients clients: [RemoteClient]) {
guard let item = sharedItem else {
return finish()
}
let profile = BrowserProfile(localName: "profile")
profile.sendItems([item], toClients: clients).uponQueue(DispatchQueue.main) { result in
profile.shutdown()
self.finish()
}
}
func clientPickerViewControllerDidCancel(_ clientPickerViewController: ClientPickerViewController) {
finish()
}
func instructionsViewControllerDidClose(_ instructionsViewController: InstructionsViewController) {
finish()
}
private func hasAccount() -> Bool {
let profile = BrowserProfile(localName: "profile")
defer {
profile.shutdown()
}
return profile.hasAccount()
}
}

View file

@ -0,0 +1,5 @@
/* 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/. */
"CFBundleDisplayName" = "Send Tab";

View file

@ -0,0 +1,320 @@
/* 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 Shared
import Storage
import SnapKit
protocol ClientPickerViewControllerDelegate {
func clientPickerViewControllerDidCancel(_ clientPickerViewController: ClientPickerViewController)
func clientPickerViewController(_ clientPickerViewController: ClientPickerViewController, didPickClients clients: [RemoteClient])
}
struct ClientPickerViewControllerUX {
static let TableHeaderRowHeight = CGFloat(50)
static let TableHeaderTextFont = UIFont.systemFont(ofSize: 16)
static let TableHeaderTextColor = UIColor.gray
static let TableHeaderTextPaddingLeft = CGFloat(20)
static let DeviceRowTintColor = UIColor(red: 0.427, green: 0.800, blue: 0.102, alpha: 1.0)
static let DeviceRowHeight = CGFloat(50)
static let DeviceRowTextFont = UIFont.systemFont(ofSize: 16)
static let DeviceRowTextPaddingLeft = CGFloat(72)
static let DeviceRowTextPaddingRight = CGFloat(50)
}
/// The ClientPickerViewController displays a list of clients associated with the provided Account.
/// The user can select a number of devices and hit the Send button.
/// This viewcontroller does not implement any specific business logic that needs to happen with the selected clients.
/// That is up to it's delegate, who can listen for cancellation and success events.
class ClientPickerViewController: UITableViewController {
var profile: Profile?
var profileNeedsShutdown = true
var clientPickerDelegate: ClientPickerViewControllerDelegate?
var reloading = true
var clients: [RemoteClient] = []
var selectedClients = NSMutableSet()
// ShareItem has been added as we are now using this class outside of the ShareTo extension to provide Share To functionality
// And in this case we need to be able to store the item we are sharing as we may not have access to the
// url later. Currently used only when sharing an item from the Tab Tray from a Preview Action.
var shareItem: ShareItem?
override func viewDidLoad() {
super.viewDidLoad()
title = NSLocalizedString("Send Tab", tableName: "SendTo", comment: "Title of the dialog that allows you to send a tab to a different device")
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(ClientPickerViewController.refresh), for: UIControlEvents.valueChanged)
navigationItem.leftBarButtonItem = UIBarButtonItem(
title: Strings.SendToCancelButton,
style: .plain,
target: self,
action: #selector(ClientPickerViewController.cancel)
)
tableView.register(ClientPickerTableViewHeaderCell.self, forCellReuseIdentifier: ClientPickerTableViewHeaderCell.CellIdentifier)
tableView.register(ClientPickerTableViewCell.self, forCellReuseIdentifier: ClientPickerTableViewCell.CellIdentifier)
tableView.register(ClientPickerNoClientsTableViewCell.self, forCellReuseIdentifier: ClientPickerNoClientsTableViewCell.CellIdentifier)
tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let refreshControl = refreshControl {
refreshControl.beginRefreshing()
let height = -(refreshControl.bounds.size.height + (self.navigationController?.navigationBar.bounds.size.height ?? 0))
self.tableView.contentOffset = CGPoint(x: 0, y: height)
}
reloadClients()
}
override func numberOfSections(in tableView: UITableView) -> Int {
if clients.count == 0 {
return 1
} else {
return 2
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if clients.count == 0 {
return 1
} else {
if section == 0 {
return 1
} else {
return clients.count
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if clients.count > 0 {
if indexPath.section == 0 {
cell = tableView.dequeueReusableCell(withIdentifier: ClientPickerTableViewHeaderCell.CellIdentifier, for: indexPath) as! ClientPickerTableViewHeaderCell
} else {
let clientCell = tableView.dequeueReusableCell(withIdentifier: ClientPickerTableViewCell.CellIdentifier, for: indexPath) as! ClientPickerTableViewCell
clientCell.nameLabel.text = clients[indexPath.row].name
clientCell.clientType = clients[indexPath.row].type == "mobile" ? ClientType.Mobile : ClientType.Desktop
clientCell.checked = selectedClients.contains(indexPath)
cell = clientCell
}
} else {
if reloading == false {
cell = tableView.dequeueReusableCell(withIdentifier: ClientPickerNoClientsTableViewCell.CellIdentifier, for: indexPath) as! ClientPickerNoClientsTableViewCell
} else {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "ClientCell")
}
}
return cell
}
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
return indexPath.section != 0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if clients.count > 0 && indexPath.section == 1 {
tableView.deselectRow(at: indexPath, animated: true)
if selectedClients.contains(indexPath) {
selectedClients.remove(indexPath)
} else {
selectedClients.add(indexPath)
}
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
navigationItem.rightBarButtonItem?.isEnabled = (selectedClients.count != 0)
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if clients.count > 0 {
if indexPath.section == 0 {
return ClientPickerViewControllerUX.TableHeaderRowHeight
} else {
return ClientPickerViewControllerUX.DeviceRowHeight
}
} else {
return tableView.frame.height
}
}
fileprivate func reloadClients() {
// If we were not given a profile, open the default profile. This happens in case we are called from an app
// extension. That also means that we need to shut down the profile, otherwise the app extension will be
// terminated when it goes into the background.
if self.profile == nil {
self.profile = BrowserProfile(localName: "profile")
self.profileNeedsShutdown = true
}
guard let profile = self.profile else {
return
}
// Re-open the profile it was shutdown. This happens when we run from an app extension, where we must
// make sure that the profile is only open for brief moments of time.
if profile.isShutdown {
profile.reopen()
}
reloading = true
profile.getClients().upon({ result in
withExtendedLifetime(profile) {
// If we are running from an app extension then make sure we shut down the profile as soon as we are
// done with it.
if self.profileNeedsShutdown {
profile.shutdown()
}
self.reloading = false
guard let c = result.successValue else {
return
}
self.clients = c
DispatchQueue.main.async {
if self.clients.count == 0 {
self.navigationItem.rightBarButtonItem = nil
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Send", tableName: "SendTo", comment: "Navigation bar button to Send the current page to a device"), style: UIBarButtonItemStyle.done, target: self, action: #selector(ClientPickerViewController.send))
self.navigationItem.rightBarButtonItem?.isEnabled = false
}
self.selectedClients.removeAllObjects()
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
}
})
}
func refresh() {
reloadClients()
}
func cancel() {
clientPickerDelegate?.clientPickerViewControllerDidCancel(self)
}
func send() {
var clients = [RemoteClient]()
for indexPath in selectedClients {
clients.append(self.clients[(indexPath as AnyObject).row])
}
clientPickerDelegate?.clientPickerViewController(self, didPickClients: clients)
// Replace the Send button with a loading indicator since it takes a while to sync
// up our changes to the server.
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
loadingIndicator.color = .darkGray
loadingIndicator.startAnimating()
let customBarButton = UIBarButtonItem(customView: loadingIndicator)
self.navigationItem.rightBarButtonItem = customBarButton
}
}
class ClientPickerTableViewHeaderCell: UITableViewCell {
static let CellIdentifier = "ClientPickerTableViewSectionHeader"
let nameLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(nameLabel)
nameLabel.font = ClientPickerViewControllerUX.TableHeaderTextFont
nameLabel.text = NSLocalizedString("Available devices:", tableName: "SendTo", comment: "Header for the list of devices table")
nameLabel.textColor = ClientPickerViewControllerUX.TableHeaderTextColor
nameLabel.snp.makeConstraints { (make) -> Void in
make.left.equalTo(ClientPickerViewControllerUX.TableHeaderTextPaddingLeft)
make.centerY.equalTo(self)
make.right.equalTo(self)
}
preservesSuperviewLayoutMargins = false
layoutMargins = UIEdgeInsets.zero
separatorInset = UIEdgeInsets.zero
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public enum ClientType: String {
case Mobile = "deviceTypeMobile"
case Desktop = "deviceTypeDesktop"
}
class ClientPickerTableViewCell: UITableViewCell {
static let CellIdentifier = "ClientPickerTableViewCell"
var nameLabel: UILabel
var checked: Bool = false {
didSet {
self.accessoryType = checked ? UITableViewCellAccessoryType.checkmark : UITableViewCellAccessoryType.none
}
}
var clientType: ClientType = ClientType.Mobile {
didSet {
self.imageView?.image = UIImage(named: clientType.rawValue)
}
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
nameLabel = UILabel()
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(nameLabel)
nameLabel.font = ClientPickerViewControllerUX.DeviceRowTextFont
nameLabel.numberOfLines = 2
nameLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
self.tintColor = ClientPickerViewControllerUX.DeviceRowTintColor
self.preservesSuperviewLayoutMargins = false
self.selectionStyle = UITableViewCellSelectionStyle.none
}
override func layoutSubviews() {
super.layoutSubviews()
nameLabel.snp.makeConstraints { (make) -> Void in
make.left.equalTo(ClientPickerViewControllerUX.DeviceRowTextPaddingLeft)
make.centerY.equalTo(self.snp.centerY)
make.right.equalTo(self.snp.right).offset(-ClientPickerViewControllerUX.DeviceRowTextPaddingRight)
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ClientPickerNoClientsTableViewCell: UITableViewCell {
static let CellIdentifier = "ClientPickerNoClientsTableViewCell"
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupHelpView(contentView,
introText: NSLocalizedString("You dont have any other devices connected to this Firefox Account available to sync.", tableName: "SendTo", comment: "Error message shown in the remote tabs panel"),
showMeText: "") // TODO We used to have a 'show me how to ...' text here. But, we cannot open web pages from the extension. So this is clear for now until we decide otherwise.
// Move the separator off screen
separatorInset = UIEdgeInsets(top: 0, left: 1000, bottom: 0, right: 0)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -0,0 +1,138 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "57x57",
"scale" : "1x"
},
{
"idiom" : "iphone",
"size" : "57x57",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "send-60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "send-60@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "50x50",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "50x50",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "72x72",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "72x72",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "send-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "send-76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "send-83.5@2x.png",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "deviceTypeDesktop.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "deviceTypeDesktop@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "deviceTypeDesktop@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "deviceTypeMobile.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "deviceTypeMobile@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "deviceTypeMobile@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "emptySync.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "emptySync@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "emptySync@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MozDevelopmentTeam</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>CFBundleDisplayName</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIcons</key>
<dict/>
<key>CFBundleIcons~ipad</key>
<dict/>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>10.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UIAppFonts</key>
<array>
<string>FiraSans-Regular.ttf</string>
</array>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO &quot;public.url&quot;).@count == 1 ).@count == 1</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
<key>NSExtensionPrincipalClass</key>
<string>ActionViewController</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,95 @@
/* 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 SnapKit
struct InstructionsViewControllerUX {
static let TopPadding = CGFloat(20)
static let TextFont = UIFont.systemFont(ofSize: UIFont.labelFontSize)
static let TextColor = UIColor(rgb: 0x555555)
static let LinkColor = UIColor.blue
}
protocol InstructionsViewControllerDelegate: class {
func instructionsViewControllerDidClose(_ instructionsViewController: InstructionsViewController)
}
private func highlightLink(_ s: NSString, withColor color: UIColor) -> NSAttributedString {
let start = s.range(of: "<")
if start.location == NSNotFound {
return NSAttributedString(string: s as String)
}
var s: NSString = s.replacingCharacters(in: start, with: "") as NSString
let end = s.range(of: ">")
s = s.replacingCharacters(in: end, with: "") as NSString
let a = NSMutableAttributedString(string: s as String)
let r = NSRange(location: start.location, length: end.location-start.location)
a.addAttribute(NSForegroundColorAttributeName, value: color, range: r)
return a
}
func setupHelpView(_ view: UIView, introText: String, showMeText: String) {
let imageView = UIImageView()
imageView.image = UIImage(named: "emptySync")
view.addSubview(imageView)
imageView.snp.makeConstraints { (make) -> Void in
make.top.equalTo(view).offset(InstructionsViewControllerUX.TopPadding)
make.centerX.equalTo(view)
}
let label1 = UILabel()
view.addSubview(label1)
label1.text = introText
label1.numberOfLines = 0
label1.lineBreakMode = NSLineBreakMode.byWordWrapping
label1.font = InstructionsViewControllerUX.TextFont
label1.textColor = InstructionsViewControllerUX.TextColor
label1.textAlignment = NSTextAlignment.center
label1.snp.makeConstraints { (make) -> Void in
make.width.equalTo(250)
make.top.equalTo(imageView.snp.bottom).offset(InstructionsViewControllerUX.TopPadding)
make.centerX.equalTo(view)
}
let label2 = UILabel()
view.addSubview(label2)
label2.numberOfLines = 0
label2.lineBreakMode = NSLineBreakMode.byWordWrapping
label2.font = InstructionsViewControllerUX.TextFont
label2.textColor = InstructionsViewControllerUX.TextColor
label2.textAlignment = NSTextAlignment.center
label2.attributedText = highlightLink(showMeText as NSString, withColor: InstructionsViewControllerUX.LinkColor)
label2.snp.makeConstraints { (make) -> Void in
make.width.equalTo(250)
make.top.equalTo(label1.snp.bottom).offset(InstructionsViewControllerUX.TopPadding)
make.centerX.equalTo(view)
}
}
class InstructionsViewController: UIViewController {
weak var delegate: InstructionsViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = []
view.backgroundColor = UIColor.white
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Close", tableName: "SendTo", comment: "Close button in top navigation bar"), style: UIBarButtonItemStyle.done, target: self, action: #selector(InstructionsViewController.close))
navigationItem.leftBarButtonItem?.accessibilityIdentifier = "InstructionsViewController.navigationItem.leftBarButtonItem"
setupHelpView(view,
introText: NSLocalizedString("You are not signed in to your Firefox Account.", tableName: "SendTo", comment: "See http://mzl.la/1ISlXnU"),
showMeText: NSLocalizedString("Please open Firefox, go to Settings and sign in to continue.", tableName: "SendTo", comment: "See http://mzl.la/1ISlXnU"))
}
func close() {
delegate?.instructionsViewControllerDidClose(self)
}
func showMeHow() {
print("Show me how") // TODO Not sure what to do or if to keep this. Waiting for UX feedback.
}
}

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6250" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="ObA-dk-sSI">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6244"/>
</dependencies>
<scenes>
<!--Send To-->
<scene sceneID="7MM-of-jgj">
<objects>
<viewController title="Send To" id="ObA-dk-sSI" customClass="ActionViewController" customModule="SendTo" customModuleProvider="target" sceneMemberID="viewController">
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<size key="freeformSize" width="320" height="528"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="252" y="-124"/>
</scene>
</scenes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination" type="retina4"/>
</simulatedMetricsContainer>
</document>

View file

@ -0,0 +1,5 @@
/* 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/. */
"CFBundleDisplayName" = "Send Tab";