mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-04 19:15:33 +08:00
chore: clean up code and architect v2
This commit is contained in:
@@ -44,6 +44,15 @@
|
||||
"revision" : "b626d3002773b1a1304166643e7f118f724b2132",
|
||||
"version" : "1.0.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "then",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/devxoul/Then",
|
||||
"state" : {
|
||||
"revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a",
|
||||
"version" : "3.0.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
||||
@@ -10,12 +10,13 @@ import UIKit
|
||||
|
||||
extension AFFiNEViewController: IntelligentsButtonDelegate {
|
||||
func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
|
||||
guard let webView else {
|
||||
assertionFailure() // ? wdym ?
|
||||
return
|
||||
}
|
||||
|
||||
IntelligentContext.shared.webView = webView!
|
||||
button.beginProgress()
|
||||
|
||||
|
||||
IntelligentContext.shared.preparePresent() {
|
||||
button.stopProgress()
|
||||
let controller = IntelligentsController()
|
||||
self.present(controller, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
edgesForExtendedLayout = []
|
||||
let intelligentsButton = installIntelligentsButton()
|
||||
intelligentsButton.delegate = self
|
||||
dismissIntelligentsButton()
|
||||
presentIntelligentsButton() // from v2.0 always visible
|
||||
}
|
||||
|
||||
override func webViewConfiguration(for instanceConfiguration: InstanceConfiguration) -> WKWebViewConfiguration {
|
||||
@@ -29,7 +29,7 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
CookiePlugin(),
|
||||
HashcashPlugin(),
|
||||
NavigationGesturePlugin(),
|
||||
IntelligentsPlugin(representController: self),
|
||||
// IntelligentsPlugin(representController: self), // no longer put in use
|
||||
NbStorePlugin(),
|
||||
]
|
||||
plugins.forEach { bridge?.registerPluginInstance($0) }
|
||||
@@ -39,15 +39,6 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
super.viewDidAppear(animated)
|
||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||
super.motionEnded(motion, with: event)
|
||||
if motion == .motionShake {
|
||||
presentIntelligentsButton()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import Capacitor
|
||||
import Foundation
|
||||
|
||||
@objc(IntelligentsPlugin)
|
||||
public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
public let identifier = "IntelligentsPlugin"
|
||||
public let jsName = "Intelligents"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
]
|
||||
public private(set) weak var representController: UIViewController?
|
||||
|
||||
init(representController: UIViewController) {
|
||||
self.representController = representController
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
representController = nil
|
||||
}
|
||||
|
||||
@objc public func presentIntelligentsButton(_ call: CAPPluginCall) {
|
||||
DispatchQueue.main.async {
|
||||
self.representController?.presentIntelligentsButton()
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func dismissIntelligentsButton(_ call: CAPPluginCall) {
|
||||
DispatchQueue.main.async {
|
||||
self.representController?.dismissIntelligentsButton()
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
//@objc(IntelligentsPlugin)
|
||||
//public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
// public let identifier = "IntelligentsPlugin"
|
||||
// public let jsName = "Intelligents"
|
||||
// public let pluginMethods: [CAPPluginMethod] = [
|
||||
// CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
// CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
// ]
|
||||
// public private(set) weak var representController: UIViewController?
|
||||
//
|
||||
// init(representController: UIViewController) {
|
||||
// self.representController = representController
|
||||
// super.init()
|
||||
// }
|
||||
//
|
||||
// deinit {
|
||||
// representController = nil
|
||||
// }
|
||||
//
|
||||
// @objc public func presentIntelligentsButton(_ call: CAPPluginCall) {
|
||||
// DispatchQueue.main.async {
|
||||
// self.representController?.presentIntelligentsButton()
|
||||
// call.resolve()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc public func dismissIntelligentsButton(_ call: CAPPluginCall) {
|
||||
// DispatchQueue.main.async {
|
||||
// self.representController?.dismissIntelligentsButton()
|
||||
// call.resolve()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -16,12 +16,14 @@ let package = Package(
|
||||
.package(path: "../AffineGraphQL"),
|
||||
.package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.18.0"),
|
||||
.package(url: "https://github.com/apple/swift-collections", from: "1.2.0"),
|
||||
.package(url: "https://github.com/SnapKit/SnapKit.git", .upToNextMajor(from: "5.0.1"))
|
||||
.package(url: "https://github.com/devxoul/Then", from: "3.0.0"),
|
||||
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.1"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Intelligents", dependencies: [
|
||||
"AffineGraphQL",
|
||||
"SnapKit",
|
||||
"Then",
|
||||
.product(name: "Apollo", package: "apollo-ios"),
|
||||
.product(name: "OrderedCollections", package: "swift-collections"),
|
||||
]),
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@ import AffineGraphQL
|
||||
import Apollo
|
||||
import Foundation
|
||||
|
||||
public enum Intelligents {
|
||||
}
|
||||
public enum Intelligents {}
|
||||
|
||||
private extension Intelligents {
|
||||
private final class URLSessionCookieClient: URLSessionClient {
|
||||
|
||||
+9
-8
@@ -5,6 +5,7 @@
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
public extension UIViewController {
|
||||
@@ -16,15 +17,15 @@ public extension UIViewController {
|
||||
let button = IntelligentsButton()
|
||||
view.addSubview(button)
|
||||
view.bringSubviewToFront(button)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
button.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
|
||||
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20 - 44),
|
||||
button.widthAnchor.constraint(equalToConstant: 50),
|
||||
button.heightAnchor.constraint(equalToConstant: 50),
|
||||
].forEach { $0.isActive = true }
|
||||
button.snp.makeConstraints { make in
|
||||
make.trailing.equalTo(view.safeAreaLayoutGuide).offset(-20)
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-20 - 44)
|
||||
make.width.height.equalTo(50)
|
||||
}
|
||||
button.transform = .init(scaleX: 0, y: 0)
|
||||
view.layoutIfNeeded()
|
||||
if view.frame != .zero {
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
return button
|
||||
}
|
||||
|
||||
|
||||
+52
-41
@@ -5,13 +5,22 @@
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
// floating button to open intelligent panel
|
||||
public class IntelligentsButton: UIView {
|
||||
let image = UIImageView()
|
||||
let background = UIView()
|
||||
let activityIndicator = UIActivityIndicatorView()
|
||||
lazy var image = UIImageView().then {
|
||||
$0.image = .init(named: "spark", in: .module, with: .none)
|
||||
$0.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
lazy var background = UIView().then {
|
||||
$0.backgroundColor = .white
|
||||
}
|
||||
|
||||
lazy var activityIndicator = UIActivityIndicatorView()
|
||||
|
||||
public weak var delegate: (any IntelligentsButtonDelegate)? = nil {
|
||||
didSet { assert(Thread.isMainThread) }
|
||||
@@ -19,44 +28,10 @@ public class IntelligentsButton: UIView {
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
background.backgroundColor = .white
|
||||
addSubview(background)
|
||||
background.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
background.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
background.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
background.topAnchor.constraint(equalTo: topAnchor),
|
||||
background.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
image.image = .init(named: "spark", in: .module, with: .none)
|
||||
image.contentMode = .scaleAspectFit
|
||||
addSubview(image)
|
||||
let imageInsetValue: CGFloat = 12
|
||||
image.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
image.leadingAnchor.constraint(equalTo: leadingAnchor, constant: imageInsetValue),
|
||||
image.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -imageInsetValue),
|
||||
image.topAnchor.constraint(equalTo: topAnchor, constant: imageInsetValue),
|
||||
image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -imageInsetValue),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(activityIndicator)
|
||||
[
|
||||
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
clipsToBounds = true
|
||||
layer.borderWidth = 2
|
||||
layer.borderColor = UIColor.gray.withAlphaComponent(0.1).cgColor
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
||||
addGestureRecognizer(tap)
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
setupViews()
|
||||
setupConstraints()
|
||||
setupGesture()
|
||||
setupAppearance()
|
||||
stopProgress()
|
||||
}
|
||||
|
||||
@@ -96,3 +71,39 @@ public class IntelligentsButton: UIView {
|
||||
image.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setup Methods
|
||||
|
||||
private extension IntelligentsButton {
|
||||
func setupViews() {
|
||||
addSubview(background)
|
||||
addSubview(image)
|
||||
addSubview(activityIndicator)
|
||||
}
|
||||
|
||||
func setupConstraints() {
|
||||
background.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
image.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(12)
|
||||
}
|
||||
|
||||
activityIndicator.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
func setupGesture() {
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
||||
addGestureRecognizer(tap)
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
func setupAppearance() {
|
||||
clipsToBounds = true
|
||||
layer.borderWidth = 2
|
||||
layer.borderColor = UIColor.gray.withAlphaComponent(0.1).cgColor
|
||||
}
|
||||
}
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// BlurTransition.swift
|
||||
// BlurTransition
|
||||
//
|
||||
// Created by 秋星桥 on 6/16/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
func presentWithFullScreenBlurTransition(_ viewController: UIViewController) {
|
||||
viewController.modalPresentationStyle = .custom
|
||||
viewController.transitioningDelegate = BlurTransitioningDelegate.shared
|
||||
present(viewController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
class BlurTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
static let shared = BlurTransitioningDelegate()
|
||||
|
||||
func animationController(
|
||||
forPresented _: UIViewController,
|
||||
presenting _: UIViewController,
|
||||
source _: UIViewController
|
||||
) -> UIViewControllerAnimatedTransitioning? {
|
||||
BlurTransitionAnimator(presenting: true)
|
||||
}
|
||||
|
||||
func animationController(
|
||||
forDismissed _: UIViewController
|
||||
) -> UIViewControllerAnimatedTransitioning? {
|
||||
BlurTransitionAnimator(presenting: false)
|
||||
}
|
||||
}
|
||||
|
||||
class BlurTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
private let presenting: Bool
|
||||
private let duration: TimeInterval = 0.5
|
||||
|
||||
private let snapshotViewTag = "snapshotView".hashValue
|
||||
private let blurViewTag = "blurView".hashValue
|
||||
|
||||
init(presenting: Bool) {
|
||||
self.presenting = presenting
|
||||
super.init()
|
||||
}
|
||||
|
||||
private func performAnimation(
|
||||
animations: @escaping () -> Void,
|
||||
completion: @escaping (Bool) -> Void
|
||||
) {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 0.75,
|
||||
initialSpringVelocity: 0.75,
|
||||
options: [.beginFromCurrentState, .allowAnimatedContent, .curveEaseInOut],
|
||||
animations: animations,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
duration
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
if presenting {
|
||||
animatePresentation(using: transitionContext)
|
||||
} else {
|
||||
animateDismissal(using: transitionContext)
|
||||
}
|
||||
}
|
||||
|
||||
private func animatePresentation(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let toViewController = transitionContext.viewController(forKey: .to),
|
||||
let fromViewController = transitionContext.viewController(forKey: .from)
|
||||
else {
|
||||
transitionContext.completeTransition(false)
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let toView = toViewController.view!
|
||||
let fromView = fromViewController.view!
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
guard let fromViewSnapshot = fromView.snapshotView(afterScreenUpdates: false) else {
|
||||
transitionContext.completeTransition(false)
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
fromViewSnapshot.frame = fromView.frame
|
||||
fromViewSnapshot.tag = snapshotViewTag
|
||||
containerView.addSubview(fromViewSnapshot)
|
||||
fromView.isHidden = true
|
||||
|
||||
let blurEffectView = UIVisualEffectView()
|
||||
blurEffectView.frame = containerView.bounds
|
||||
blurEffectView.tag = blurViewTag
|
||||
containerView.addSubview(blurEffectView)
|
||||
|
||||
toView.frame = containerView.bounds
|
||||
toView.alpha = 0
|
||||
toView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
|
||||
containerView.addSubview(toView)
|
||||
|
||||
performAnimation(animations: {
|
||||
blurEffectView.effect = UIBlurEffect(style: .systemMaterial)
|
||||
fromViewSnapshot.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||
toView.alpha = 1
|
||||
toView.transform = .identity
|
||||
}) { _ in
|
||||
let success = !transitionContext.transitionWasCancelled
|
||||
if !success {
|
||||
assertionFailure()
|
||||
fromView.isHidden = false
|
||||
fromViewSnapshot.removeFromSuperview()
|
||||
blurEffectView.removeFromSuperview()
|
||||
toView.removeFromSuperview()
|
||||
}
|
||||
transitionContext.completeTransition(success)
|
||||
}
|
||||
}
|
||||
|
||||
private func animateDismissal(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let fromViewController = transitionContext.viewController(forKey: .from),
|
||||
let toViewController = transitionContext.viewController(forKey: .to)
|
||||
else {
|
||||
transitionContext.completeTransition(false)
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let fromView = fromViewController.view!
|
||||
let toView = toViewController.view!
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
guard let fromViewSnapshot = containerView.viewWithTag(snapshotViewTag),
|
||||
let blurEffectView = containerView.viewWithTag(blurViewTag) as? UIVisualEffectView
|
||||
else {
|
||||
toView.isHidden = false
|
||||
assertionFailure()
|
||||
transitionContext.completeTransition(true)
|
||||
return
|
||||
}
|
||||
|
||||
performAnimation(animations: {
|
||||
fromViewSnapshot.transform = .identity
|
||||
blurEffectView.effect = nil
|
||||
fromView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
|
||||
fromView.alpha = 0
|
||||
}) { _ in
|
||||
let success = !transitionContext.transitionWasCancelled
|
||||
if success {
|
||||
toView.isHidden = false
|
||||
fromViewSnapshot.removeFromSuperview()
|
||||
blurEffectView.removeFromSuperview()
|
||||
} else {
|
||||
assertionFailure()
|
||||
fromView.transform = .identity
|
||||
fromView.alpha = 1
|
||||
}
|
||||
transitionContext.completeTransition(success)
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// IntelligentsController.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/17/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public class IntelligentsController: UINavigationController {
|
||||
public init() {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
modalPresentationStyle = .custom
|
||||
transitioningDelegate = BlurTransitioningDelegate.shared
|
||||
setNavigationBarHidden(true, animated: false)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .systemBackground
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
setNavigationBarHidden(true, animated: animated)
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// UIHostingView.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/12/13.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class UIHostingView<Content: View>: UIView {
|
||||
private let hostingViewController: UIHostingController<Content>
|
||||
|
||||
var rootView: Content {
|
||||
get { hostingViewController.rootView }
|
||||
set { hostingViewController.rootView = newValue }
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
hostingViewController.view.intrinsicContentSize
|
||||
}
|
||||
|
||||
init(rootView: Content) {
|
||||
hostingViewController = UIHostingController(rootView: rootView)
|
||||
hostingViewController.edgesForExtendedLayout = []
|
||||
hostingViewController.extendedLayoutIncludesOpaqueBars = false
|
||||
super.init(frame: .zero)
|
||||
|
||||
hostingViewController.view?.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(hostingViewController.view)
|
||||
if let view = hostingViewController.view {
|
||||
view.removeFromSuperview()
|
||||
view.backgroundColor = .clear
|
||||
view.isOpaque = false
|
||||
addSubview(view)
|
||||
let constraints = [
|
||||
view.topAnchor.constraint(equalTo: topAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
view.leftAnchor.constraint(equalTo: leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: rightAnchor),
|
||||
]
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
hostingViewController.sizeThatFits(in: size)
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// IntelligentContext.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/17/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
public class IntelligentContext {
|
||||
// shared across the app, we expect our app to have a single context and webview
|
||||
public static let shared = IntelligentContext()
|
||||
|
||||
public var webView: WKWebView!
|
||||
|
||||
private init() {}
|
||||
|
||||
public func preparePresent(_ completion: @escaping () -> Void) {
|
||||
// used to gathering information, populate content from webview, etc.
|
||||
// TODO: if needed
|
||||
completion()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user