chore: clean up code and architect v2

This commit is contained in:
Lakr
2025-06-17 13:28:20 +08:00
parent 6d0516175d
commit f6b281d12f
12 changed files with 345 additions and 156 deletions
@@ -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"),
]),
@@ -5,8 +5,7 @@ import AffineGraphQL
import Apollo
import Foundation
public enum Intelligents {
}
public enum Intelligents {}
private extension Intelligents {
private final class URLSessionCookieClient: URLSessionClient {
@@ -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
}
@@ -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
}
}
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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()
}
}