Dactyloidae/mobile/ios/Client/Frontend/Browser/ClipboardBarDisplayHandler.swift
2026-06-26 21:04:09 -07:00

162 lines
5.8 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 Foundation
import Shared
public struct ClipboardBarToastUX {
static let ToastDelay = DispatchTimeInterval.milliseconds(4000)
}
protocol ClipboardBarDisplayHandlerDelegate: class {
func shouldDisplay(clipboardBar bar: ButtonToast)
}
class ClipboardBarDisplayHandler: NSObject, URLChangeDelegate {
weak var delegate: (ClipboardBarDisplayHandlerDelegate & SettingsDelegate)?
weak var settingsDelegate: SettingsDelegate?
weak var tabManager: TabManager?
private var sessionStarted = true
private var sessionRestored = false
private var firstTabLoaded = false
private var prefs: Prefs
private var lastDisplayedURL: String?
private weak var firstTab: Tab?
var clipboardToast: ButtonToast?
init(prefs: Prefs, tabManager: TabManager) {
self.prefs = prefs
self.tabManager = tabManager
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(SELUIPasteboardChanged), name: NSNotification.Name.UIPasteboardChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SELAppWillEnterForegroundNotification), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SELDidRestoreSession), name: NotificationDidRestoreSession, object: nil)
}
@objc private func SELUIPasteboardChanged() {
// UIPasteboardChanged gets triggered when calling UIPasteboard.general.
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIPasteboardChanged, object: nil)
UIPasteboard.general.asyncURL().uponQueue(.main) { res in
defer {
NotificationCenter.default.addObserver(self, selector: #selector(self.SELUIPasteboardChanged), name: NSNotification.Name.UIPasteboardChanged, object: nil)
}
guard let copiedURL: URL? = res.successValue,
let url = copiedURL else {
return
}
self.lastDisplayedURL = url.absoluteString
}
}
@objc private func SELAppWillEnterForegroundNotification() {
sessionStarted = true
checkIfShouldDisplayBar()
}
private func observeURLForFirstTab(firstTab: Tab) {
if firstTab.webView == nil {
// Nothing to do; bail out.
firstTabLoaded = true
return
}
self.firstTab = firstTab
firstTab.observeURLChanges(delegate: self)
}
@objc private func SELDidRestoreSession() {
DispatchQueue.main.sync {
if let tabManager = self.tabManager,
let firstTab = tabManager.selectedTab {
self.observeURLForFirstTab(firstTab: firstTab)
} else {
firstTabLoaded = true
}
NotificationCenter.default.removeObserver(self, name: NotificationDidRestoreSession, object: nil)
sessionRestored = true
checkIfShouldDisplayBar()
}
}
func tab(_ tab: Tab, urlDidChangeTo url: URL) {
// Ugly hack to ensure we wait until we're finished restoring the session on the first tab
// before checking if we should display the clipboard bar.
guard sessionRestored,
!url.absoluteString.startsWith("\(WebServer.sharedInstance.base)/about/sessionrestore?history=") else {
return
}
tab.removeURLChangeObserver(delegate: self)
firstTabLoaded = true
checkIfShouldDisplayBar()
}
private func shouldDisplayBar(_ copiedURL: String) -> Bool {
if !sessionStarted ||
!sessionRestored ||
!firstTabLoaded ||
isClipboardURLAlreadyDisplayed(copiedURL) ||
self.prefs.intForKey(IntroViewControllerSeenProfileKey) == nil {
return false
}
sessionStarted = false
return true
}
// If we already displayed this URL on the previous session, or in an already open
// tab, we shouldn't display it again
private func isClipboardURLAlreadyDisplayed(_ clipboardURL: String) -> Bool {
if lastDisplayedURL == clipboardURL {
return true
}
if let url = URL(string: clipboardURL),
let _ = tabManager?.getTabFor(url) {
return true
}
return false
}
func checkIfShouldDisplayBar() {
guard self.prefs.boolForKey("showClipboardBar") ?? false else {
// There's no point in doing any of this work unless the
// user has asked for it in settings.
return
}
UIPasteboard.general.asyncURL().uponQueue(.main) { res in
guard let copiedURL: URL? = res.successValue,
let url = copiedURL else {
return
}
let absoluteString = url.absoluteString
guard self.shouldDisplayBar(absoluteString) else {
return
}
self.lastDisplayedURL = absoluteString
self.clipboardToast =
ButtonToast(
labelText: Strings.GoToCopiedLink,
descriptionText: url.absoluteDisplayString,
buttonText: Strings.GoButtonTittle,
completion: { buttonPressed in
if buttonPressed {
self.delegate?.settingsOpenURLInNewTab(url)
}
})
if let toast = self.clipboardToast {
self.delegate?.shouldDisplay(clipboardBar: toast)
}
}
}
}