Skip to content

Commit

Permalink
Bug 1130495 - First version of Synced (Remote) Tabs home panel. r=bni…
Browse files Browse the repository at this point in the history
…cholson
  • Loading branch information
ncalexan committed Mar 19, 2015
2 parents c6f9eb3 + 85eaca7 commit d71b023
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 3 deletions.
8 changes: 7 additions & 1 deletion Client.xcodeproj/project.pbxproj
Expand Up @@ -113,6 +113,8 @@
2FDE87521ABA3EA0005317B1 /* TimeConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3444561AB22A4B00FD9731 /* TimeConstants.swift */; };
2FDE87531ABA3EB4005317B1 /* ColorUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D35275151AA8D13B00E9C906 /* ColorUtils.swift */; };
2FDE87541ABA3EBB005317B1 /* KeyboardHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39999A81AA01D65005AED21 /* KeyboardHelper.swift */; };
2FDE87FE1ABB3817005317B1 /* RemoteTabsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FDE87FD1ABB3817005317B1 /* RemoteTabsPanel.swift */; };
2FDE881B1ABB3921005317B1 /* RemoteTabsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FDE87FD1ABB3817005317B1 /* RemoteTabsPanel.swift */; };
2FEBABAF1AB3659000DB5728 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEBABAE1AB3659000DB5728 /* ResultTests.swift */; };
39A362D91AAF5ECE00F47390 /* XCGLogger.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39A362D41AAF5E2C00F47390 /* XCGLogger.framework */; };
39A362DA1AAF5ECE00F47390 /* XCGLogger.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 39A362D41AAF5E2C00F47390 /* XCGLogger.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -672,6 +674,7 @@
2FA0AF801AAFB62E0083E9FA /* HawkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HawkHelper.swift; sourceTree = "<group>"; };
2FA0AFA21AAFF1E20083E9FA /* HawkHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HawkHelperTests.swift; path = FxATests/HawkHelperTests.swift; sourceTree = "<group>"; };
2FDB10921A9FBEC5006CF312 /* ProfilePrefsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfilePrefsTests.swift; sourceTree = "<group>"; };
2FDE87FD1ABB3817005317B1 /* RemoteTabsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteTabsPanel.swift; sourceTree = "<group>"; };
2FEBABAE1AB3659000DB5728 /* ResultTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ResultTests.swift; path = SyncTests/ResultTests.swift; sourceTree = "<group>"; };
39A362B21AAF5E2B00F47390 /* XCGLogger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = XCGLogger.xcodeproj; path = Carthage/Checkouts/XCGLogger/XCGLogger/Library/XCGLogger.xcodeproj; sourceTree = "<group>"; };
4A59BF410BBD9B3BE71F4C7C /* TestHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHistory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1393,13 +1396,14 @@
F84B22211A09122500AAB793 /* Home */ = {
isa = PBXGroup;
children = (
F84B22221A09122500AAB793 /* HomePanelViewController.swift */,
F84B22261A09127C00AAB793 /* Home.xcassets */,
F84B22221A09122500AAB793 /* HomePanelViewController.swift */,
D30B101D1AA7F9C600C01CA3 /* HomePanels.swift */,
59A6825233896FC846499289 /* HistoryPanel.swift */,
59A6839879D615FC1C0D71CE /* BookmarksPanel.swift */,
0BF648101A9C54E900BA963C /* TopSitesPanel.swift */,
59A685F4EAD19EDEC854BCA4 /* ReaderPanel.swift */,
2FDE87FD1ABB3817005317B1 /* RemoteTabsPanel.swift */,
);
path = Home;
sourceTree = "<group>";
Expand Down Expand Up @@ -2091,6 +2095,7 @@
E42CCDE31A23A6F900B794D3 /* Clients.swift in Sources */,
D30B101E1AA7F9C600C01CA3 /* HomePanels.swift in Sources */,
F84B22041A0910F600AAB793 /* AppDelegate.swift in Sources */,
2FDE87FE1ABB3817005317B1 /* RemoteTabsPanel.swift in Sources */,
D31A0FC71A65D6D000DC8C7E /* SearchSuggestClient.swift in Sources */,
D38B2D311A8D96D00040E6B5 /* GCDWebServerConnection.m in Sources */,
D38B2D401A8D96D00040E6B5 /* GCDWebServerFileRequest.m in Sources */,
Expand Down Expand Up @@ -2172,6 +2177,7 @@
2F834D1B1A80629A006A0B7B /* FxAContentViewController.swift in Sources */,
D3FA77971A43B5390010CD32 /* OpenSearch.swift in Sources */,
2FA7EAB41AB0BC6F00CE5347 /* FxAClient10.swift in Sources */,
2FDE881B1ABB3921005317B1 /* RemoteTabsPanel.swift in Sources */,
28786E551AB0F5FA009EA9EF /* DeferredTests.swift in Sources */,
D3FA777B1A43B2990010CD32 /* SearchTests.swift in Sources */,
D38B2D441A8D96D00040E6B5 /* GCDWebServerMultiPartFormRequest.m in Sources */,
Expand Down
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "sync_desktop.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "sync_mobile.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions Client/Frontend/Home/HomePanels.swift
Expand Up @@ -43,6 +43,15 @@ class HomePanels {
imageName: "history",
accessibilityLabel: NSLocalizedString("History", comment: "Panel accessibility label")),

HomePanelDescriptor(
makeViewController: { profile in
let controller = RemoteTabsPanel()
controller.profile = profile
return controller
},
imageName: "tabs",
accessibilityLabel: NSLocalizedString("Synced tabs", comment: "Panel accessibility label")),

HomePanelDescriptor(
makeViewController: { profile in
let controller = ReadingListPanel()
Expand Down
130 changes: 130 additions & 0 deletions Client/Frontend/Home/RemoteTabsPanel.swift
@@ -0,0 +1,130 @@
/* 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 Snap
import Storage

private struct RemoteTabsPanelUX {
static let HeaderHeight: CGFloat = SiteTableViewControllerUX.RowHeight // Not HeaderHeight!
static let RowHeight: CGFloat = SiteTableViewControllerUX.RowHeight
}

private let RemoteClientIdentifier = "RemoteClient"
private let RemoteTabIdentifier = "RemoteTab"

/**
* Display a tree hierarchy of remote clients and tabs, like:
* client
* tab
* tab
* client
* tab
* tab
* This is not a SiteTableViewController because it is inherently tree-like and not list-like;
* a technical detail is that STVC is backed by a Cursor and this is backed by a richer data
* structure. However, the styling here should agree with STVC where possible.
*/
class RemoteTabsPanel: UITableViewController, HomePanel {
weak var homePanelDelegate: HomePanelDelegate? = nil
var profile: Profile!

private var clients: [RemoteClient]?

private func tabAtIndexPath(indexPath: NSIndexPath) -> RemoteTab? {
return clients?[indexPath.section].tabs[indexPath.item]
}

override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(TwoLineHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: RemoteClientIdentifier)
tableView.registerClass(TwoLineTableViewCell.self, forCellReuseIdentifier: RemoteTabIdentifier)
tableView.rowHeight = RemoteTabsPanelUX.RowHeight
tableView.separatorInset = UIEdgeInsetsZero

refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: "SELrefresh", forControlEvents: UIControlEvents.ValueChanged)
}

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.SELrefresh()
}

@objc private func SELrefresh() {
self.refreshControl?.beginRefreshing()
profile.remoteClientsAndTabs.getClientsAndTabs { clients in
self.refreshControl?.endRefreshing()
self.clients = clients
// Maybe show a background view.
let tableView = self.tableView
if self.clients == nil || self.clients!.isEmpty {
tableView.backgroundView = UIView()
tableView.backgroundView?.frame = tableView.frame
// TODO: Populate background view with UX-approved content.
tableView.backgroundView?.backgroundColor = UIColor.redColor()
// Hide dividing lines.
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
} else {
tableView.backgroundView = nil
// Show dividing lines.
tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
}
tableView.reloadData()
}
}

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return clients?.count ?? 0
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return clients?[section].tabs.count ?? 0
}

override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return RemoteTabsPanelUX.HeaderHeight
}

override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let client = clients?[section] {
let view = tableView.dequeueReusableHeaderFooterViewWithIdentifier(RemoteClientIdentifier) as TwoLineHeaderFooterView
view.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: RemoteTabsPanelUX.HeaderHeight)
view.textLabel.text = client.name
// TODO: allow localization; convert timestamp to relative timestring.
view.detailTextLabel.text = "Last synced: \(String(client.lastModified))"
if client.type == "desktop" {
view.imageView.image = UIImage(named: "deviceTypeDesktop")
} else {
view.imageView.image = UIImage(named: "deviceTypeMobile")
}
return view
} else {
return nil
}
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(RemoteTabIdentifier, forIndexPath: indexPath) as TwoLineTableViewCell
if let tab = tabAtIndexPath(indexPath) {
// TODO: Populate image with cached favicons.
if let title = tab.title {
cell.textLabel?.text = title
cell.detailTextLabel?.text = tab.URL.absoluteString
} else {
cell.textLabel?.text = tab.URL.absoluteString
cell.detailTextLabel?.text = nil
}
}
return cell
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)

if let tab = tabAtIndexPath(indexPath) {
homePanelDelegate?.homePanel(self, didSelectURL: tab.URL)
}
}
}
9 changes: 7 additions & 2 deletions Client/Frontend/Widgets/SiteTableViewController.swift
Expand Up @@ -5,6 +5,11 @@
import UIKit
import Storage

struct SiteTableViewControllerUX {
static let HeaderHeight = CGFloat(25)
static let RowHeight = CGFloat(58)
}

private class SiteTableViewHeader : UITableViewHeaderFooterView {
// I can't get drawRect to play nicely with the glass background. As a fallback
// we just use views for the top and bottom borders.
Expand Down Expand Up @@ -102,10 +107,10 @@ class SiteTableViewController: UIViewController, UITableViewDelegate, UITableVie
}

func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 25
return SiteTableViewControllerUX.HeaderHeight
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 58
return SiteTableViewControllerUX.RowHeight
}
}
52 changes: 52 additions & 0 deletions Client/Frontend/Widgets/TwoLineCell.swift
Expand Up @@ -64,6 +64,58 @@ class TwoLineCollectionViewCell: UICollectionViewCell {
}
}

class TwoLineHeaderFooterView: UITableViewHeaderFooterView {
private let twoLineHelper: TwoLineCellHelper!

// UITableViewHeaderFooterView includes textLabel and detailTextLabel, so we can't override
// them. Unfortunately, they're also used in ways that interfere with us just using them: I get
// hard crashes in layout if I just use them; it seems there's a battle over adding to the
// contentView. So we add our own members, and cover up the other ones.
let _textLabel = UILabel()
let _detailTextLabel = UILabel()

let imageView = UIImageView()

// Yes, this is strange.
override var textLabel: UILabel {
return _textLabel
}

// Yes, this is strange.
override var detailTextLabel: UILabel {
return _detailTextLabel
}

override init() {
super.init()
}

override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
}

override init(frame: CGRect) {
super.init(frame: frame)

contentView.addSubview(_textLabel)
contentView.addSubview(_detailTextLabel)
contentView.addSubview(imageView)

twoLineHelper = TwoLineCellHelper(container: self, textLabel: _textLabel, detailTextLabel: _detailTextLabel, imageView: imageView)

layoutMargins = UIEdgeInsetsZero
}

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func layoutSubviews() {
super.layoutSubviews()
twoLineHelper.layoutSubviews()
}
}

private class TwoLineCellHelper {
private let container: UIView
let textLabel: UILabel
Expand Down
3 changes: 3 additions & 0 deletions FxA/FxA.xcodeproj/project.pbxproj
Expand Up @@ -794,6 +794,7 @@
28F9520F19D0F9FB00DCE892 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
Expand Down Expand Up @@ -821,6 +822,7 @@
28F9521019D0F9FB00DCE892 /* FennecAurora */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
Expand Down Expand Up @@ -931,6 +933,7 @@
E4D438FC1A8D32B6003FCF55 /* FennecNightly */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
Expand Down
9 changes: 9 additions & 0 deletions Providers/Profile.swift
Expand Up @@ -83,6 +83,7 @@ protocol Profile {
var history: History { get }
var favicons: Favicons { get }
var readingList: ReadingList { get }
var remoteClientsAndTabs: RemoteClientsAndTabs { get }
var passwords: Passwords { get }

// I got really weird EXC_BAD_ACCESS errors on a non-null reference when I made this a getter.
Expand Down Expand Up @@ -136,6 +137,10 @@ public class MockProfile: Profile {
return SQLiteReadingList(files: self.files)
}()

lazy var remoteClientsAndTabs: RemoteClientsAndTabs = {
return SQLiteRemoteClientsAndTabs(files: self.files)
}()

lazy var passwords: Passwords = {
return MockPasswords(files: self.files)
}()
Expand Down Expand Up @@ -223,6 +228,10 @@ public class BrowserProfile: Profile {
return SQLiteReadingList(files: self.files)
}()

lazy var remoteClientsAndTabs: RemoteClientsAndTabs = {
return MockRemoteClientsAndTabs()
}()

lazy var passwords: Passwords = {
return SQLitePasswords(files: self.files)
}()
Expand Down

0 comments on commit d71b023

Please sign in to comment.