mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-24 05:13:07 +09:00
593 lines
25 KiB
Swift
593 lines
25 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 Storage
|
||
import Shared
|
||
import XCGLogger
|
||
|
||
private let log = Logger.browserLogger
|
||
|
||
let BookmarkStatusChangedNotification = "BookmarkStatusChangedNotification"
|
||
|
||
// MARK: - Placeholder strings for Bug 1232810.
|
||
|
||
let deleteWarningTitle = NSLocalizedString("This folder isn’t empty.", tableName: "BookmarkPanelDeleteConfirm", comment: "Title of the confirmation alert when the user tries to delete a folder that still contains bookmarks and/or folders.")
|
||
let deleteWarningDescription = NSLocalizedString("Are you sure you want to delete it and its contents?", tableName: "BookmarkPanelDeleteConfirm", comment: "Main body of the confirmation alert when the user tries to delete a folder that still contains bookmarks and/or folders.")
|
||
let deleteCancelButtonLabel = NSLocalizedString("Cancel", tableName: "BookmarkPanelDeleteConfirm", comment: "Button label to cancel deletion when the user tried to delete a non-empty folder.")
|
||
let deleteDeleteButtonLabel = NSLocalizedString("Delete", tableName: "BookmarkPanelDeleteConfirm", comment: "Button label for the button that deletes a folder and all of its children.")
|
||
|
||
// Placeholder strings for Bug 1248034
|
||
let emptyBookmarksText = NSLocalizedString("Bookmarks you save will show up here.", comment: "Status label for the empty Bookmarks state.")
|
||
|
||
// MARK: - UX constants.
|
||
|
||
struct BookmarksPanelUX {
|
||
static let BookmarkFolderHeaderViewChevronInset: CGFloat = 10
|
||
static let BookmarkFolderChevronSize: CGFloat = 20
|
||
static let BookmarkFolderChevronLineWidth: CGFloat = 2.0
|
||
static let BookmarkFolderTextColor = UIColor(red: 92/255, green: 92/255, blue: 92/255, alpha: 1.0)
|
||
static let BookmarkFolderBGColor = UIColor(rgb: 0xf7f8f7).withAlphaComponent(0.3)
|
||
static let WelcomeScreenPadding: CGFloat = 15
|
||
static let WelcomeScreenItemTextColor = UIColor.gray
|
||
static let WelcomeScreenItemWidth = 170
|
||
static let SeparatorRowHeight: CGFloat = 0.5
|
||
static let IconSize: CGFloat = 23
|
||
static let IconBorderColor = UIColor(white: 0, alpha: 0.1)
|
||
static let IconBorderWidth: CGFloat = 0.5
|
||
}
|
||
|
||
class BookmarksPanel: SiteTableViewController, HomePanel {
|
||
weak var homePanelDelegate: HomePanelDelegate?
|
||
var source: BookmarksModel?
|
||
var parentFolders = [BookmarkFolder]()
|
||
var bookmarkFolder: BookmarkFolder?
|
||
var refreshControl: UIRefreshControl?
|
||
|
||
fileprivate lazy var longPressRecognizer: UILongPressGestureRecognizer = {
|
||
return UILongPressGestureRecognizer(target: self, action: #selector(BookmarksPanel.longPress(_:)))
|
||
}()
|
||
fileprivate lazy var emptyStateOverlayView: UIView = self.createEmptyStateOverlayView()
|
||
|
||
fileprivate let BookmarkFolderCellIdentifier = "BookmarkFolderIdentifier"
|
||
fileprivate let BookmarkSeparatorCellIdentifier = "BookmarkSeparatorIdentifier"
|
||
fileprivate let BookmarkFolderHeaderViewIdentifier = "BookmarkFolderHeaderIdentifier"
|
||
|
||
init() {
|
||
super.init(nibName: nil, bundle: nil)
|
||
NotificationCenter.default.addObserver(self, selector: #selector(BookmarksPanel.notificationReceived(_:)), name: NotificationFirefoxAccountChanged, object: nil)
|
||
|
||
self.tableView.register(SeparatorTableCell.self, forCellReuseIdentifier: BookmarkSeparatorCellIdentifier)
|
||
self.tableView.register(BookmarkFolderTableViewCell.self, forCellReuseIdentifier: BookmarkFolderCellIdentifier)
|
||
self.tableView.register(BookmarkFolderTableViewHeader.self, forHeaderFooterViewReuseIdentifier: BookmarkFolderHeaderViewIdentifier)
|
||
}
|
||
|
||
required init?(coder aDecoder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
tableView.addGestureRecognizer(longPressRecognizer)
|
||
|
||
self.tableView.accessibilityIdentifier = "Bookmarks List"
|
||
|
||
self.refreshControl = UIRefreshControl()
|
||
self.tableView.addSubview(refreshControl!)
|
||
}
|
||
|
||
override func viewWillAppear(_ animated: Bool) {
|
||
super.viewWillAppear(animated)
|
||
|
||
refreshControl?.addTarget(self, action: #selector(BookmarksPanel.refreshBookmarks), for: .valueChanged)
|
||
|
||
loadData()
|
||
}
|
||
|
||
override func viewDidDisappear(_ animated: Bool) {
|
||
super.viewDidDisappear(animated)
|
||
refreshControl?.removeTarget(self, action: #selector(BookmarksPanel.refreshBookmarks), for: .valueChanged)
|
||
}
|
||
|
||
func loadData() {
|
||
// If we've not already set a source for this panel, fetch a new model from
|
||
// the root; otherwise, just use the existing source to select a folder.
|
||
guard let source = self.source else {
|
||
// Get all the bookmarks split by folders
|
||
if let bookmarkFolder = bookmarkFolder {
|
||
profile.bookmarks.modelFactory >>== { $0.modelForFolder(bookmarkFolder).upon(self.onModelFetched) }
|
||
} else {
|
||
profile.bookmarks.modelFactory >>== { $0.modelForRoot().upon(self.onModelFetched) }
|
||
}
|
||
return
|
||
}
|
||
|
||
if let bookmarkFolder = bookmarkFolder {
|
||
source.selectFolder(bookmarkFolder).upon(onModelFetched)
|
||
} else {
|
||
source.selectFolder(BookmarkRoots.MobileFolderGUID).upon(onModelFetched)
|
||
}
|
||
}
|
||
|
||
func notificationReceived(_ notification: Notification) {
|
||
switch notification.name {
|
||
case NotificationFirefoxAccountChanged:
|
||
self.reloadData()
|
||
break
|
||
default:
|
||
// no need to do anything at all
|
||
log.warning("Received unexpected notification \(notification.name)")
|
||
break
|
||
}
|
||
}
|
||
|
||
@objc fileprivate func refreshBookmarks() {
|
||
profile.syncManager.mirrorBookmarks().upon { (_) in
|
||
DispatchQueue.main.async {
|
||
self.loadData()
|
||
self.refreshControl?.endRefreshing()
|
||
}
|
||
}
|
||
}
|
||
|
||
fileprivate func createEmptyStateOverlayView() -> UIView {
|
||
let overlayView = UIView()
|
||
overlayView.backgroundColor = UIColor.white
|
||
|
||
let logoImageView = UIImageView(image: UIImage(named: "emptyBookmarks"))
|
||
overlayView.addSubview(logoImageView)
|
||
logoImageView.snp.makeConstraints { make in
|
||
make.centerX.equalTo(overlayView)
|
||
|
||
// Sets proper top constraint for iPhone 6 in portait and for iPad.
|
||
make.centerY.equalTo(overlayView).offset(HomePanelUX.EmptyTabContentOffset).priority(100)
|
||
|
||
// Sets proper top constraint for iPhone 4, 5 in portrait.
|
||
make.top.greaterThanOrEqualTo(overlayView).offset(50)
|
||
}
|
||
|
||
let welcomeLabel = UILabel()
|
||
overlayView.addSubview(welcomeLabel)
|
||
welcomeLabel.text = emptyBookmarksText
|
||
welcomeLabel.textAlignment = NSTextAlignment.center
|
||
welcomeLabel.font = DynamicFontHelper.defaultHelper.DeviceFontLight
|
||
welcomeLabel.textColor = BookmarksPanelUX.WelcomeScreenItemTextColor
|
||
welcomeLabel.numberOfLines = 0
|
||
welcomeLabel.adjustsFontSizeToFitWidth = true
|
||
|
||
welcomeLabel.snp.makeConstraints { make in
|
||
make.centerX.equalTo(overlayView)
|
||
make.top.equalTo(logoImageView.snp.bottom).offset(BookmarksPanelUX.WelcomeScreenPadding)
|
||
make.width.equalTo(BookmarksPanelUX.WelcomeScreenItemWidth)
|
||
}
|
||
|
||
return overlayView
|
||
}
|
||
|
||
fileprivate func updateEmptyPanelState() {
|
||
if source?.current.count == 0 && source?.current.guid == BookmarkRoots.MobileFolderGUID {
|
||
if self.emptyStateOverlayView.superview == nil {
|
||
self.view.addSubview(self.emptyStateOverlayView)
|
||
self.view.bringSubview(toFront: self.emptyStateOverlayView)
|
||
self.emptyStateOverlayView.snp.makeConstraints { make -> Void in
|
||
make.edges.equalTo(self.tableView)
|
||
}
|
||
}
|
||
} else {
|
||
self.emptyStateOverlayView.removeFromSuperview()
|
||
}
|
||
}
|
||
|
||
fileprivate func onModelFetched(_ result: Maybe<BookmarksModel>) {
|
||
guard let model = result.successValue else {
|
||
self.onModelFailure(result.failureValue as Any)
|
||
return
|
||
}
|
||
self.onNewModel(model)
|
||
}
|
||
|
||
fileprivate func onNewModel(_ model: BookmarksModel) {
|
||
if Thread.current.isMainThread {
|
||
self.source = model
|
||
self.tableView.reloadData()
|
||
return
|
||
}
|
||
|
||
DispatchQueue.main.async {
|
||
self.source = model
|
||
self.tableView.reloadData()
|
||
self.updateEmptyPanelState()
|
||
}
|
||
}
|
||
|
||
fileprivate func onModelFailure(_ e: Any) {
|
||
log.error("Error: failed to get data: \(e)")
|
||
}
|
||
|
||
override func reloadData() {
|
||
self.source?.reloadData().upon(onModelFetched)
|
||
}
|
||
|
||
@objc fileprivate func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
|
||
guard longPressGestureRecognizer.state == UIGestureRecognizerState.began else { return }
|
||
let touchPoint = longPressGestureRecognizer.location(in: tableView)
|
||
guard let indexPath = tableView.indexPathForRow(at: touchPoint) else { return }
|
||
presentContextMenu(for: indexPath)
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||
return source?.current.count ?? 0
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||
guard let source = source, let bookmark = source.current[indexPath.row] else { return super.tableView(tableView, cellForRowAt: indexPath) }
|
||
switch bookmark {
|
||
case let item as BookmarkItem:
|
||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||
if item.title.isEmpty {
|
||
cell.textLabel?.text = item.url
|
||
} else {
|
||
cell.textLabel?.text = item.title
|
||
}
|
||
if let url = bookmark.favicon?.url.asURL, url.scheme == "asset" {
|
||
cell.imageView?.image = UIImage(named: url.host!)
|
||
} else {
|
||
cell.imageView?.layer.borderColor = BookmarksPanelUX.IconBorderColor.cgColor
|
||
cell.imageView?.layer.borderWidth = BookmarksPanelUX.IconBorderWidth
|
||
let bookmarkURL = URL(string: item.url)
|
||
cell.imageView?.setIcon(bookmark.favicon, forURL: bookmarkURL, completed: { (color, url) in
|
||
if bookmarkURL == url {
|
||
cell.imageView?.image = cell.imageView?.image?.createScaled(CGSize(width: BookmarksPanelUX.IconSize, height: BookmarksPanelUX.IconSize))
|
||
cell.imageView?.backgroundColor = color
|
||
cell.imageView?.contentMode = .center
|
||
}
|
||
})
|
||
}
|
||
return cell
|
||
case is BookmarkSeparator:
|
||
return tableView.dequeueReusableCell(withIdentifier: BookmarkSeparatorCellIdentifier, for: indexPath)
|
||
case let bookmark as BookmarkFolder:
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: BookmarkFolderCellIdentifier, for: indexPath)
|
||
cell.textLabel?.text = bookmark.title
|
||
return cell
|
||
default:
|
||
// This should never happen.
|
||
return super.tableView(tableView, cellForRowAt: indexPath)
|
||
}
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: IndexPath) {
|
||
if let cell = cell as? BookmarkFolderTableViewCell {
|
||
cell.textLabel?.font = DynamicFontHelper.defaultHelper.DeviceFontHistoryPanel
|
||
}
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||
// Don't show a header for the root
|
||
if source == nil || parentFolders.isEmpty {
|
||
return nil
|
||
}
|
||
guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: BookmarkFolderHeaderViewIdentifier) as? BookmarkFolderTableViewHeader else { return nil }
|
||
|
||
// register as delegate to ensure we get notified when the user interacts with this header
|
||
if header.delegate == nil {
|
||
header.delegate = self
|
||
}
|
||
|
||
if parentFolders.count == 1 {
|
||
header.textLabel?.text = NSLocalizedString("Bookmarks", comment: "Panel accessibility label")
|
||
} else if let parentFolder = parentFolders.last {
|
||
header.textLabel?.text = parentFolder.title
|
||
}
|
||
|
||
return header
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||
if let it = self.source?.current[indexPath.row], it is BookmarkSeparator {
|
||
return BookmarksPanelUX.SeparatorRowHeight
|
||
}
|
||
|
||
return super.tableView(tableView, heightForRowAt: indexPath)
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||
// Don't show a header for the root. If there's no root (i.e. source == nil), we'll also show no header.
|
||
if source == nil || parentFolders.isEmpty {
|
||
return 0
|
||
}
|
||
|
||
return SiteTableViewControllerUX.RowHeight
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
|
||
if let header = view as? BookmarkFolderTableViewHeader {
|
||
// for some reason specifying the font in header view init is being ignored, so setting it here
|
||
header.textLabel?.font = DynamicFontHelper.defaultHelper.DeviceFontHistoryPanel
|
||
}
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, hasFullWidthSeparatorForRowAtIndexPath indexPath: IndexPath) -> Bool {
|
||
// Show a full-width border for cells above separators, so they don't have a weird step.
|
||
// Separators themselves already have a full-width border, but let's force the issue
|
||
// just in case.
|
||
let this = self.source?.current[indexPath.row]
|
||
if (indexPath.row + 1) < (self.source?.current.count)! {
|
||
let below = self.source?.current[indexPath.row + 1]
|
||
if this is BookmarkSeparator || below is BookmarkSeparator {
|
||
return true
|
||
}
|
||
}
|
||
return super.tableView(tableView, hasFullWidthSeparatorForRowAtIndexPath: indexPath)
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
|
||
tableView.deselectRow(at: indexPath, animated: false)
|
||
guard let source = source else {
|
||
return
|
||
}
|
||
|
||
let bookmark = source.current[indexPath.row]
|
||
|
||
switch bookmark {
|
||
case let item as BookmarkItem:
|
||
homePanelDelegate?.homePanel(self, didSelectURLString: item.url, visitType: VisitType.bookmark)
|
||
LeanPlumClient.shared.track(event: .openedBookmark)
|
||
UnifiedTelemetry.recordEvent(category: .action, method: .open, object: .bookmark, value: .bookmarksPanel)
|
||
break
|
||
|
||
case let folder as BookmarkFolder:
|
||
log.debug("Selected \(folder.guid)")
|
||
let nextController = BookmarksPanel()
|
||
nextController.parentFolders = parentFolders + [source.current]
|
||
nextController.bookmarkFolder = folder
|
||
nextController.homePanelDelegate = self.homePanelDelegate
|
||
nextController.profile = self.profile
|
||
source.modelFactory.uponQueue(DispatchQueue.main) { maybe in
|
||
guard let factory = maybe.successValue else {
|
||
// Nothing we can do.
|
||
return
|
||
}
|
||
let specificFactory = factory.factoryForIndex(indexPath.row, inFolder: source.current)
|
||
nextController.source = BookmarksModel(modelFactory: specificFactory, root: folder)
|
||
self.navigationController?.pushViewController(nextController, animated: true)
|
||
}
|
||
break
|
||
|
||
default:
|
||
// You can't do anything with separators.
|
||
break
|
||
}
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: IndexPath) {
|
||
// Intentionally blank. Required to use UITableViewRowActions
|
||
}
|
||
|
||
private func editingStyleforRow(atIndexPath indexPath: IndexPath) -> UITableViewCellEditingStyle {
|
||
guard let source = source else {
|
||
return .none
|
||
}
|
||
|
||
if source.current[indexPath.row] is BookmarkSeparator {
|
||
// Because the deletion block is too big.
|
||
return .none
|
||
}
|
||
|
||
if source.current.itemIsEditableAtIndex(indexPath.row) {
|
||
return .delete
|
||
}
|
||
|
||
return .none
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, editingStyleForRowAtIndexPath indexPath: IndexPath) -> UITableViewCellEditingStyle {
|
||
return editingStyleforRow(atIndexPath: indexPath)
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, editActionsForRowAtIndexPath indexPath: IndexPath) -> [AnyObject]? {
|
||
let editingStyle = editingStyleforRow(atIndexPath: indexPath)
|
||
guard let source = self.source, editingStyle == .delete else {
|
||
return nil
|
||
}
|
||
|
||
let title = NSLocalizedString("Delete", tableName: "BookmarkPanel", comment: "Action button for deleting bookmarks in the bookmarks panel.")
|
||
|
||
let delete = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: title, handler: { (action, indexPath) in
|
||
self.deleteBookmark(indexPath: indexPath, source: source)
|
||
UnifiedTelemetry.recordEvent(category: .action, method: .delete, object: .bookmark, value: .bookmarksPanel, extras: ["gesture": "swipe"])
|
||
})
|
||
|
||
return [delete]
|
||
}
|
||
|
||
func pinTopSite(_ site: Site) {
|
||
_ = profile.history.addPinnedTopSite(site).value
|
||
}
|
||
|
||
func deleteBookmark(indexPath: IndexPath, source: BookmarksModel) {
|
||
guard let bookmark = source.current[indexPath.row] else {
|
||
return
|
||
}
|
||
|
||
assert(!(bookmark is BookmarkFolder))
|
||
if bookmark is BookmarkFolder {
|
||
// TODO: check whether the folder is empty (excluding separators). If it isn't
|
||
// then we must ask the user to confirm. Bug 1232810.
|
||
log.debug("Not deleting folder.")
|
||
return
|
||
}
|
||
|
||
log.debug("Removing rows \(indexPath).")
|
||
|
||
// Block to do this -- this is UI code.
|
||
guard let factory = source.modelFactory.value.successValue else {
|
||
log.error("Couldn't get model factory. This is unexpected.")
|
||
self.onModelFailure(DatabaseError(description: "Unable to get factory."))
|
||
return
|
||
}
|
||
|
||
let specificFactory = factory.factoryForIndex(indexPath.row, inFolder: source.current)
|
||
if let err = specificFactory.removeByGUID(bookmark.guid).value.failureValue {
|
||
log.debug("Failed to remove \(bookmark.guid).")
|
||
self.onModelFailure(err)
|
||
return
|
||
}
|
||
|
||
self.tableView.beginUpdates()
|
||
self.source = source.removeGUIDFromCurrent(bookmark.guid)
|
||
self.tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.left)
|
||
self.tableView.endUpdates()
|
||
self.updateEmptyPanelState()
|
||
|
||
NotificationCenter.default.post(name: NSNotification.Name(rawValue: BookmarkStatusChangedNotification), object: bookmark, userInfo: ["added": false]
|
||
)
|
||
}
|
||
}
|
||
|
||
extension BookmarksPanel: HomePanelContextMenu {
|
||
func presentContextMenu(for site: Site, with indexPath: IndexPath, completionHandler: @escaping () -> PhotonActionSheet?) {
|
||
guard let contextMenu = completionHandler() else { return }
|
||
self.present(contextMenu, animated: true, completion: nil)
|
||
}
|
||
|
||
func getSiteDetails(for indexPath: IndexPath) -> Site? {
|
||
guard let bookmarkItem = source?.current[indexPath.row] as? BookmarkItem else { return nil }
|
||
let site = Site(url: bookmarkItem.url, title: bookmarkItem.title, bookmarked: true, guid: bookmarkItem.guid)
|
||
site.icon = bookmarkItem.favicon
|
||
return site
|
||
}
|
||
|
||
func getContextMenuActions(for site: Site, with indexPath: IndexPath) -> [PhotonActionSheetItem]? {
|
||
guard var actions = getDefaultContextMenuActions(for: site, homePanelDelegate: homePanelDelegate) else { return nil }
|
||
|
||
let pinTopSite = PhotonActionSheetItem(title: Strings.PinTopsiteActionTitle, iconString: "action_pin", handler: { action in
|
||
self.pinTopSite(site)
|
||
})
|
||
|
||
actions.append(pinTopSite)
|
||
|
||
// Only local bookmarks can be removed
|
||
guard let source = source else { return nil }
|
||
if source.current.itemIsEditableAtIndex(indexPath.row) {
|
||
let removeAction = PhotonActionSheetItem(title: Strings.RemoveBookmarkContextMenuTitle, iconString: "action_bookmark_remove", handler: { action in
|
||
self.deleteBookmark(indexPath: indexPath, source: source)
|
||
UnifiedTelemetry.recordEvent(category: .action, method: .delete, object: .bookmark, value: .bookmarksPanel, extras: ["gesture": "long-press"])
|
||
})
|
||
actions.append(removeAction)
|
||
}
|
||
return actions
|
||
}
|
||
}
|
||
|
||
private protocol BookmarkFolderTableViewHeaderDelegate {
|
||
func didSelectHeader()
|
||
}
|
||
|
||
extension BookmarksPanel: BookmarkFolderTableViewHeaderDelegate {
|
||
fileprivate func didSelectHeader() {
|
||
_ = self.navigationController?.popViewController(animated: true)
|
||
}
|
||
}
|
||
|
||
class BookmarkFolderTableViewCell: TwoLineTableViewCell {
|
||
|
||
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
|
||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||
self.backgroundColor = BookmarksPanelUX.BookmarkFolderBGColor
|
||
textLabel?.backgroundColor = UIColor.clear
|
||
textLabel?.tintColor = BookmarksPanelUX.BookmarkFolderTextColor
|
||
|
||
imageView?.image = UIImage(named: "bookmarkFolder")
|
||
accessoryType = UITableViewCellAccessoryType.disclosureIndicator
|
||
separatorInset = UIEdgeInsets.zero
|
||
}
|
||
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
}
|
||
|
||
required init?(coder aDecoder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
}
|
||
|
||
fileprivate class BookmarkFolderTableViewHeader: UITableViewHeaderFooterView {
|
||
var delegate: BookmarkFolderTableViewHeaderDelegate?
|
||
|
||
lazy var titleLabel: UILabel = {
|
||
let label = UILabel()
|
||
label.textColor = UIConstants.HighlightBlue
|
||
return label
|
||
}()
|
||
|
||
lazy var chevron: ChevronView = {
|
||
let chevron = ChevronView(direction: .left)
|
||
chevron.tintColor = UIConstants.HighlightBlue
|
||
chevron.lineWidth = BookmarksPanelUX.BookmarkFolderChevronLineWidth
|
||
return chevron
|
||
}()
|
||
|
||
lazy var topBorder: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = SiteTableViewControllerUX.HeaderBorderColor
|
||
return view
|
||
}()
|
||
|
||
lazy var bottomBorder: UIView = {
|
||
let view = UIView()
|
||
view.backgroundColor = SiteTableViewControllerUX.HeaderBorderColor
|
||
return view
|
||
}()
|
||
|
||
override var textLabel: UILabel? {
|
||
return titleLabel
|
||
}
|
||
|
||
override init(reuseIdentifier: String?) {
|
||
super.init(reuseIdentifier: reuseIdentifier)
|
||
|
||
isUserInteractionEnabled = true
|
||
|
||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(BookmarkFolderTableViewHeader.viewWasTapped(_:)))
|
||
tapGestureRecognizer.numberOfTapsRequired = 1
|
||
addGestureRecognizer(tapGestureRecognizer)
|
||
|
||
addSubview(topBorder)
|
||
addSubview(bottomBorder)
|
||
contentView.addSubview(chevron)
|
||
contentView.addSubview(titleLabel)
|
||
|
||
chevron.snp.makeConstraints { make in
|
||
make.left.equalTo(contentView).offset(BookmarksPanelUX.BookmarkFolderHeaderViewChevronInset)
|
||
make.centerY.equalTo(contentView)
|
||
make.size.equalTo(BookmarksPanelUX.BookmarkFolderChevronSize)
|
||
}
|
||
|
||
titleLabel.snp.makeConstraints { make in
|
||
make.left.equalTo(chevron.snp.right).offset(BookmarksPanelUX.BookmarkFolderHeaderViewChevronInset)
|
||
make.right.greaterThanOrEqualTo(contentView).offset(-BookmarksPanelUX.BookmarkFolderHeaderViewChevronInset)
|
||
make.centerY.equalTo(contentView)
|
||
}
|
||
|
||
topBorder.snp.makeConstraints { make in
|
||
make.left.right.equalTo(self)
|
||
make.top.equalTo(self).offset(-0.5)
|
||
make.height.equalTo(0.5)
|
||
}
|
||
|
||
bottomBorder.snp.makeConstraints { make in
|
||
make.left.right.bottom.equalTo(self)
|
||
make.height.equalTo(0.5)
|
||
}
|
||
}
|
||
|
||
required init?(coder aDecoder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
@objc fileprivate func viewWasTapped(_ gestureRecognizer: UITapGestureRecognizer) {
|
||
delegate?.didSelectHeader()
|
||
}
|
||
}
|