<commit_before>/* 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

private let BookmarkNodeCellIdentifier = "BookmarkNodeCellIdentifier"
private let BookmarkSeparatorCellIdentifier = "BookmarkSeparatorCellIdentifier"

private struct BookmarksPanelUX {
    static let FolderIconSize = CGSize(width: 20, height: 20)
    static let RowFlashDelay: TimeInterval = 0.4
}

let LocalizedRootBookmarkFolderStrings = [
    BookmarkRoots.MenuFolderGUID: Strings.BookmarksFolderTitleMenu,
    BookmarkRoots.ToolbarFolderGUID: Strings.BookmarksFolderTitleToolbar,
    BookmarkRoots.UnfiledFolderGUID: Strings.BookmarksFolderTitleUnsorted,
    BookmarkRoots.MobileFolderGUID: Strings.BookmarksFolderTitleMobile
]

fileprivate class SeparatorTableViewCell: OneLineTableViewCell {
    override func applyTheme() {
        super.applyTheme()

        backgroundColor = UIColor.theme.tableView.headerBackground
    }
}

class BookmarksPanel: SiteTableViewController, LibraryPanel {
    enum BookmarksSection: Int, CaseIterable {
        case bookmarks
        case recent
    }

    var libraryPanelDelegate: LibraryPanelDelegate?

    let bookmarkFolderGUID: GUID

    var editBarButtonItem: UIBarButtonItem!
    var doneBarButtonItem: UIBarButtonItem!
    var newBarButtonItem: UIBarButtonItem!

    var bookmarkFolder: BookmarkFolder?
    var bookmarkNodes = [BookmarkNode]()
    var recentBookmarks = [BookmarkNode]()

    fileprivate var flashLastRowOnNextReload = false

    init(profile: Profile, bookmarkFolderGUID: GUID = BookmarkRoots.RootGUID) {
        self.bookmarkFolderGUID = bookmarkFolderGUID

        super.init(profile: profile)

        [ Notification.Name.FirefoxAccountChanged,
          Notification.Name.DynamicFontChanged ].forEach {
            NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived), name: $0, object: nil)
        }

        self.tableView.register(OneLineTableViewCell.self, forCellReuseIdentifier: BookmarkNodeCellIdentifier)
        self.tableView.register(SeparatorTableViewCell.self, forCellReuseIdentifier: BookmarkSeparatorCellIdentifier)
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableViewLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressTableView))
        tableView.addGestureRecognizer(tableViewLongPressRecognizer)
        tableView.accessibilityIdentifier = "Bookmarks List"
        tableView.allowsSelectionDuringEditing = true

        self.editBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit) { _ in
            self.tableView.setEditing(true, animated: true)
            self.navigationItem.leftBarButtonItem = self.newBarButtonItem
            self.navigationItem.rightBarButtonItem = self.doneBarButtonItem
        }

        self.doneBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done) { _ in
            self.tableView.setEditing(false, animated: true)
            self.navigationItem.leftBarButtonItem = nil
            self.navigationItem.rightBarButtonItem = self.editBarButtonItem
        }

        self.newBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add) { _ in
            let newBookmark = PhotonActionSheetItem(title: Strings.BookmarksNewBookmark, iconString: "action_bookmark", handler: { action in
                guard let bookmarkFolder = self.bookmarkFolder else {
                    return
                }

                let detailController = BookmarkDetailPanel(profile: self.profile, withNewBookmarkNodeType: .bookmark, parentBookmarkFolder: bookmarkFolder)
                self.navigationController?.pushViewController(detailController, animated: true)
            })

            let newFolder = PhotonActionSheetItem(title: Strings.BookmarksNewFolder, iconString: "bookmarkFolder", handler: { action in
                guard let bookmarkFolder = self.bookmarkFolder else {
                    return
                }

                let detailController = BookmarkDetailPanel(profile: self.profile, withNewBookmarkNodeType: .folder, parentBookmarkFolder: bookmarkFolder)
                self.navigationController?.pushViewController(detailController, animated: true)
            })

            let newSeparator = PhotonActionSheetItem(title: Strings.BookmarksNewSeparator, iconString: "nav-menu", handler: { action in
                let centerVisibleRow = self.centerVisibleRow()

                self.profile.places.createSeparator(parentGUID: self.bookmarkFolderGUID, position: UInt32(centerVisibleRow)) >>== { guid in
                    self.profile.places.getBookmark(guid: guid).uponQueue(.main) { result in
                        guard let bookmarkNode = result.successValue, let bookmarkSeparator = bookmarkNode as? BookmarkSeparator else {
                            return
                        }

                        let indexPath = IndexPath(row: centerVisibleRow, section: BookmarksSection.bookmarks.rawValue)
                        self.tableView.beginUpdates()
                        self.bookmarkNodes.insert(bookmarkSeparator, at: centerVisibleRow)
                        self.tableView.insertRows(at: [indexPath], with: .automatic)
                        self.tableView.endUpdates()

                        self.flashRow(at: indexPath)
                    }
                }
            })

            let sheet = PhotonActionSheet(actions: [[newBookmark, newFolder, newSeparator]])
            sheet.modalPresentationStyle = .overFullScreen
            sheet.modalTransitionStyle = .crossDissolve
            self.present(sheet, animated: true)
        }

        if bookmarkFolderGUID != BookmarkRoots.RootGUID {
            navigationItem.rightBarButtonItem = editBarButtonItem
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        if let backButtonView = self.backButtonView() {
            let backButtonViewLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressBackButtonView))
            backButtonView.addGestureRecognizer(backButtonViewLongPressRecognizer)
        }
    }

    override func applyTheme() {
        super.applyTheme()

        if let current = navigationController?.visibleViewController as? Themeable, current !== self {
            current.applyTheme()
        }
    }

    override func reloadData() {
        profile.places.getBookmarksTree(rootGUID: bookmarkFolderGUID, recursive: false).uponQueue(.main) { result in

            guard let folder = result.successValue as? BookmarkFolder else {
                // TODO: Handle error case?
                self.bookmarkFolder = nil
                self.bookmarkNodes = []
                self.recentBookmarks = []
                return
            }

            self.bookmarkFolder = folder
            self.bookmarkNodes = folder.children ?? []

            if folder.guid == BookmarkRoots.RootGUID {
                self.profile.places.getRecentBookmarks(limit: 20).uponQueue(.main) { result in
                    self.recentBookmarks = result.successValue ?? []
                    self.tableView.reloadData()
                }
            } else {
                self.recentBookmarks = []
                self.tableView.reloadData()
            }

            if self.flashLastRowOnNextReload {
                self.flashLastRowOnNextReload = false

                let lastIndexPath = IndexPath(row: self.bookmarkNodes.count - 1, section: BookmarksSection.bookmarks.rawValue)
                DispatchQueue.main.asyncAfter(deadline: .now() + BookmarksPanelUX.RowFlashDelay) {
                    self.flashRow(at: lastIndexPath)
                }
            }
        }
    }

    fileprivate func backButtonView() -> UIView? {
        let navigationBarContentView = navigationController?.navigationBar.subviews.find({ $0.description.starts(with: "<_UINavigationBarContentView:") })
        return navigationBarContentView?.subviews.find({ $0.description.starts(with: "<_UIButtonBarButton:") })
    }<commit_msg>Fix #5144 - [Bookmarks]Long press on the 'Back' button does not work after entering/exiting Edit mode (#5153)<commit_after>/* 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

private let BookmarkNodeCellIdentifier = "BookmarkNodeCellIdentifier"
private let BookmarkSeparatorCellIdentifier = "BookmarkSeparatorCellIdentifier"

private struct BookmarksPanelUX {
    static let FolderIconSize = CGSize(width: 20, height: 20)
    static let RowFlashDelay: TimeInterval = 0.4
}

let LocalizedRootBookmarkFolderStrings = [
    BookmarkRoots.MenuFolderGUID: Strings.BookmarksFolderTitleMenu,
    BookmarkRoots.ToolbarFolderGUID: Strings.BookmarksFolderTitleToolbar,
    BookmarkRoots.UnfiledFolderGUID: Strings.BookmarksFolderTitleUnsorted,
    BookmarkRoots.MobileFolderGUID: Strings.BookmarksFolderTitleMobile
]

fileprivate class SeparatorTableViewCell: OneLineTableViewCell {
    override func applyTheme() {
        super.applyTheme()

        backgroundColor = UIColor.theme.tableView.headerBackground
    }
}

class BookmarksPanel: SiteTableViewController, LibraryPanel {
    enum BookmarksSection: Int, CaseIterable {
        case bookmarks
        case recent
    }

    var libraryPanelDelegate: LibraryPanelDelegate?

    let bookmarkFolderGUID: GUID

    var editBarButtonItem: UIBarButtonItem!
    var doneBarButtonItem: UIBarButtonItem!
    var newBarButtonItem: UIBarButtonItem!

    var bookmarkFolder: BookmarkFolder?
    var bookmarkNodes = [BookmarkNode]()
    var recentBookmarks = [BookmarkNode]()

    fileprivate var flashLastRowOnNextReload = false

    init(profile: Profile, bookmarkFolderGUID: GUID = BookmarkRoots.RootGUID) {
        self.bookmarkFolderGUID = bookmarkFolderGUID

        super.init(profile: profile)

        [ Notification.Name.FirefoxAccountChanged,
          Notification.Name.DynamicFontChanged ].forEach {
            NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived), name: $0, object: nil)
        }

        self.tableView.register(OneLineTableViewCell.self, forCellReuseIdentifier: BookmarkNodeCellIdentifier)
        self.tableView.register(SeparatorTableViewCell.self, forCellReuseIdentifier: BookmarkSeparatorCellIdentifier)
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableViewLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressTableView))
        tableView.addGestureRecognizer(tableViewLongPressRecognizer)
        tableView.accessibilityIdentifier = "Bookmarks List"
        tableView.allowsSelectionDuringEditing = true

        self.editBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit) { _ in
            self.tableView.setEditing(true, animated: true)
            self.navigationItem.leftBarButtonItem = self.newBarButtonItem
            self.navigationItem.rightBarButtonItem = self.doneBarButtonItem
        }

        self.doneBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done) { _ in
            self.tableView.setEditing(false, animated: true)
            self.navigationItem.leftBarButtonItem = nil
            self.navigationItem.rightBarButtonItem = self.editBarButtonItem
            self.setupBackButtonGestureRecognizer()
        }

        self.newBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add) { _ in
            let newBookmark = PhotonActionSheetItem(title: Strings.BookmarksNewBookmark, iconString: "action_bookmark", handler: { action in
                guard let bookmarkFolder = self.bookmarkFolder else {
                    return
                }

                let detailController = BookmarkDetailPanel(profile: self.profile, withNewBookmarkNodeType: .bookmark, parentBookmarkFolder: bookmarkFolder)
                self.navigationController?.pushViewController(detailController, animated: true)
            })

            let newFolder = PhotonActionSheetItem(title: Strings.BookmarksNewFolder, iconString: "bookmarkFolder", handler: { action in
                guard let bookmarkFolder = self.bookmarkFolder else {
                    return
                }

                let detailController = BookmarkDetailPanel(profile: self.profile, withNewBookmarkNodeType: .folder, parentBookmarkFolder: bookmarkFolder)
                self.navigationController?.pushViewController(detailController, animated: true)
            })

            let newSeparator = PhotonActionSheetItem(title: Strings.BookmarksNewSeparator, iconString: "nav-menu", handler: { action in
                let centerVisibleRow = self.centerVisibleRow()

                self.profile.places.createSeparator(parentGUID: self.bookmarkFolderGUID, position: UInt32(centerVisibleRow)) >>== { guid in
                    self.profile.places.getBookmark(guid: guid).uponQueue(.main) { result in
                        guard let bookmarkNode = result.successValue, let bookmarkSeparator = bookmarkNode as? BookmarkSeparator else {
                            return
                        }

                        let indexPath = IndexPath(row: centerVisibleRow, section: BookmarksSection.bookmarks.rawValue)
                        self.tableView.beginUpdates()
                        self.bookmarkNodes.insert(bookmarkSeparator, at: centerVisibleRow)
                        self.tableView.insertRows(at: [indexPath], with: .automatic)
                        self.tableView.endUpdates()

                        self.flashRow(at: indexPath)
                    }
                }
            })

            let sheet = PhotonActionSheet(actions: [[newBookmark, newFolder, newSeparator]])
            sheet.modalPresentationStyle = .overFullScreen
            sheet.modalTransitionStyle = .crossDissolve
            self.present(sheet, animated: true)
        }

        if bookmarkFolderGUID != BookmarkRoots.RootGUID {
            navigationItem.rightBarButtonItem = editBarButtonItem
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        setupBackButtonGestureRecognizer()
    }

    override func applyTheme() {
        super.applyTheme()

        if let current = navigationController?.visibleViewController as? Themeable, current !== self {
            current.applyTheme()
        }
    }

    override func reloadData() {
        profile.places.getBookmarksTree(rootGUID: bookmarkFolderGUID, recursive: false).uponQueue(.main) { result in

            guard let folder = result.successValue as? BookmarkFolder else {
                // TODO: Handle error case?
                self.bookmarkFolder = nil
                self.bookmarkNodes = []
                self.recentBookmarks = []
                return
            }

            self.bookmarkFolder = folder
            self.bookmarkNodes = folder.children ?? []

            if folder.guid == BookmarkRoots.RootGUID {
                self.profile.places.getRecentBookmarks(limit: 20).uponQueue(.main) { result in
                    self.recentBookmarks = result.successValue ?? []
                    self.tableView.reloadData()
                }
            } else {
                self.recentBookmarks = []
                self.tableView.reloadData()
            }

            if self.flashLastRowOnNextReload {
                self.flashLastRowOnNextReload = false

                let lastIndexPath = IndexPath(row: self.bookmarkNodes.count - 1, section: BookmarksSection.bookmarks.rawValue)
                DispatchQueue.main.asyncAfter(deadline: .now() + BookmarksPanelUX.RowFlashDelay) {
                    self.flashRow(at: lastIndexPath)
                }
            }
        }
    }

    fileprivate func setupBackButtonGestureRecognizer() {
        if let backButtonView = self.backButtonView() {
            let backButtonViewLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressBackButtonView))
            backButtonView.addGestureRecognizer(backButtonViewLongPressRecognizer)
        }
    }

    fileprivate func backButtonView() -> UIView? {
        let navigationBarContentView = navigationController?.navigationBar.subviews.find({ $0.description.starts(with: "<_UINavigationBarContentView:") })
        return navigationBarContentView?.subviews.find({ $0.description.starts(with: "<_UIButtonBarButton:") })
    }
{'fraction_non_alphanumeric': 0.07544413277232352, 'fraction_numerical': 0.002571294997662459, 'mean_word_length': 2.6754725085910653, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1449587', 'n_tokens_mistral': 4534, 'n_tokens_neox': 4327, 'n_words': 874}