mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
WIP: 完成 AI 框架嵌入文档的动画
This commit is contained in:
@@ -1,28 +1,92 @@
|
|||||||
import UIKit
|
|
||||||
import Capacitor
|
import Capacitor
|
||||||
import Intelligents
|
import Intelligents
|
||||||
|
import UIKit
|
||||||
|
|
||||||
class AFFiNEViewController: CAPBridgeViewController {
|
class AFFiNEViewController: CAPBridgeViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
webView?.allowsBackForwardNavigationGestures = true
|
webView?.allowsBackForwardNavigationGestures = true
|
||||||
self.navigationController?.navigationBar.isHidden = true
|
navigationController?.navigationBar.isHidden = true
|
||||||
installIntelligentsButton()
|
extendedLayoutIncludesOpaqueBars = false
|
||||||
|
edgesForExtendedLayout = []
|
||||||
|
let intelligentsButton = installIntelligentsButton()
|
||||||
|
intelligentsButton.delegate = self
|
||||||
|
dismissIntelligentsButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func capacitorDidLoad() {
|
override func capacitorDidLoad() {
|
||||||
bridge?.registerPluginInstance(CookiePlugin())
|
let plugins: [CAPPlugin] = [
|
||||||
bridge?.registerPluginInstance(HashcashPlugin())
|
CookiePlugin(),
|
||||||
bridge?.registerPluginInstance(IntelligentsPlugin(ui: self))
|
HashcashPlugin(),
|
||||||
|
IntelligentsPlugin(representController: self),
|
||||||
|
]
|
||||||
|
plugins.forEach { bridge?.registerPluginInstance($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||||
self.dismissIntelligentsButton()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AFFiNEViewController: IntelligentsButtonDelegate {
|
||||||
|
func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
|
||||||
|
guard let webView else {
|
||||||
|
assertionFailure() // ? wdym ?
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
button.beginProgress()
|
||||||
|
|
||||||
|
let script = "return await window.getCurrentDocContentInMarkdown();"
|
||||||
|
webView.callAsyncJavaScript(
|
||||||
|
script,
|
||||||
|
arguments: [:],
|
||||||
|
in: nil,
|
||||||
|
in: .page
|
||||||
|
) { result in
|
||||||
|
button.stopProgress()
|
||||||
|
webView.resignFirstResponder()
|
||||||
|
|
||||||
|
if case let .failure(error) = result {
|
||||||
|
print("[?] \(self) script error: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .success(content) = result,
|
||||||
|
let res = content as? String
|
||||||
|
{
|
||||||
|
print("[*] \(self) received document with \(res.count) characters")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.openIntelligentsSheet(withContext: res)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.openSimpleChat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openIntelligentsSheet(withContext context: String) {
|
||||||
|
guard let view = webView else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = context
|
||||||
|
let focus = IntelligentsFocusApertureView()
|
||||||
|
focus.prepareAnimationWith(
|
||||||
|
capturingTargetContentView: view,
|
||||||
|
coveringRootViewController: self
|
||||||
|
)
|
||||||
|
focus.executeAnimationKickIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func openSimpleChat() {
|
||||||
|
let targetController = IntelligentsChatController()
|
||||||
|
presentIntoCurrentContext(withTargetController: targetController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +1,47 @@
|
|||||||
import UIKit
|
|
||||||
import Capacitor
|
import Capacitor
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
var window: UIWindow?
|
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
// Override point for customization after application launch.
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func applicationWillResignActive(_: UIApplication) {
|
||||||
// Override point for customization after application launch.
|
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||||
return true
|
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillResignActive(_ application: UIApplication) {
|
func applicationDidEnterBackground(_: UIApplication) {
|
||||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
func applicationWillEnterForeground(_: UIApplication) {
|
||||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
func applicationDidBecomeActive(_: UIApplication) {
|
||||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
func applicationWillTerminate(_: UIApplication) {
|
||||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ application: UIApplication) {
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
// Called when the app was launched with a url. Feel free to add additional processing here,
|
||||||
}
|
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
||||||
|
ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
||||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
}
|
||||||
// Called when the app was launched with a url. Feel free to add additional processing here,
|
|
||||||
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
|
||||||
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
|
||||||
// Called when the app was launched with an activity, including Universal Links.
|
|
||||||
// Feel free to add additional processing here, but if you want the App API to support
|
|
||||||
// tracking app url opens, make sure to keep this call
|
|
||||||
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||||
|
// Called when the app was launched with an activity, including Universal Links.
|
||||||
|
// Feel free to add additional processing here, but if you want the App API to support
|
||||||
|
// tracking app url opens, make sure to keep this call
|
||||||
|
ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-16
@@ -18,20 +18,20 @@ extension UIView {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
func debugFrame() {
|
func debugFrame() {
|
||||||
layer.borderWidth = 1
|
layer.borderWidth = 1
|
||||||
layer.borderColor = [
|
layer.borderColor = [
|
||||||
UIColor.red,
|
UIColor.red,
|
||||||
.green,
|
.green,
|
||||||
.blue,
|
.blue,
|
||||||
.yellow,
|
.yellow,
|
||||||
.cyan,
|
.cyan,
|
||||||
.magenta,
|
.magenta,
|
||||||
.orange,
|
.orange,
|
||||||
].map(\.cgColor).randomElement()
|
].map(\.cgColor).randomElement()
|
||||||
subviews.forEach { $0.debugFrame() }
|
subviews.forEach { $0.debugFrame() }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIViewController {
|
public extension UIViewController {
|
||||||
func presentIntoCurrentContext(withTargetController targetController: UIViewController, animated: Bool = true) {
|
func presentIntoCurrentContext(withTargetController targetController: UIViewController, animated: Bool = true) {
|
||||||
if let nav = self as? UINavigationController {
|
if let nav = self as? UINavigationController {
|
||||||
nav.pushViewController(targetController, animated: animated)
|
nav.pushViewController(targetController, animated: animated)
|
||||||
|
|||||||
+2
@@ -44,6 +44,7 @@ public extension UIViewController {
|
|||||||
button.alpha = 0
|
button.alpha = 0
|
||||||
button.isHidden = false
|
button.isHidden = false
|
||||||
button.setNeedsLayout()
|
button.setNeedsLayout()
|
||||||
|
button.stopProgress()
|
||||||
view.layoutIfNeeded()
|
view.layoutIfNeeded()
|
||||||
|
|
||||||
UIView.animate(
|
UIView.animate(
|
||||||
@@ -63,6 +64,7 @@ public extension UIViewController {
|
|||||||
guard let button = findIntelligentsButton() else { return }
|
guard let button = findIntelligentsButton() else { return }
|
||||||
print("[*] \(button) is calling \(#function)")
|
print("[*] \(button) is calling \(#function)")
|
||||||
|
|
||||||
|
button.stopProgress()
|
||||||
button.setNeedsLayout()
|
button.setNeedsLayout()
|
||||||
view.layoutIfNeeded()
|
view.layoutIfNeeded()
|
||||||
UIView.animate(
|
UIView.animate(
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// IntelligentsButton+Delegate.swift
|
||||||
|
// Intelligents
|
||||||
|
//
|
||||||
|
// Created by 秋星桥 on 2024/11/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol IntelligentsButtonDelegate: AnyObject {
|
||||||
|
func onIntelligentsButtonTapped(_ button: IntelligentsButton)
|
||||||
|
}
|
||||||
+27
-10
@@ -11,6 +11,11 @@ import UIKit
|
|||||||
public class IntelligentsButton: UIView {
|
public class IntelligentsButton: UIView {
|
||||||
let image = UIImageView()
|
let image = UIImageView()
|
||||||
let background = UIView()
|
let background = UIView()
|
||||||
|
let activityIndicator = UIActivityIndicatorView()
|
||||||
|
|
||||||
|
public weak var delegate: (any IntelligentsButtonDelegate)? = nil {
|
||||||
|
didSet { assert(Thread.isMainThread) }
|
||||||
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
@@ -38,9 +43,11 @@ public class IntelligentsButton: UIView {
|
|||||||
image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -imageInsetValue),
|
image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -imageInsetValue),
|
||||||
].forEach { $0.isActive = true }
|
].forEach { $0.isActive = true }
|
||||||
|
|
||||||
// layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
|
addSubview(activityIndicator)
|
||||||
// layer.shadowOffset = CGSize(width: 0, height: 0)
|
[
|
||||||
// layer.shadowRadius = 8
|
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||||
|
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||||
|
].forEach { $0.isActive = true }
|
||||||
|
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
layer.borderWidth = 2
|
layer.borderWidth = 2
|
||||||
@@ -49,6 +56,8 @@ public class IntelligentsButton: UIView {
|
|||||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
||||||
addGestureRecognizer(tap)
|
addGestureRecognizer(tap)
|
||||||
isUserInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
|
|
||||||
|
stopProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
@@ -56,18 +65,26 @@ public class IntelligentsButton: UIView {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
delegate = nil
|
||||||
|
}
|
||||||
|
|
||||||
override public func layoutSubviews() {
|
override public func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
layer.cornerRadius = bounds.width / 2
|
layer.cornerRadius = bounds.width / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapped() {
|
@objc func tapped() {
|
||||||
guard let controller = parentViewController else {
|
delegate?.onIntelligentsButtonTapped(self)
|
||||||
assertionFailure()
|
}
|
||||||
return
|
|
||||||
}
|
public func beginProgress() {
|
||||||
let targetController = IntelligentsChatController()
|
activityIndicator.startAnimating()
|
||||||
controller.presentIntoCurrentContext(withTargetController: targetController)
|
activityIndicator.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func stopProgress() {
|
||||||
|
activityIndicator.stopAnimating()
|
||||||
|
activityIndicator.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -25,7 +25,7 @@ class AttachmentBannerView: UIScrollView {
|
|||||||
height: attachmentSize
|
height: attachmentSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class AttachmentBannerView: UIScrollView {
|
|||||||
|
|
||||||
showsHorizontalScrollIndicator = false
|
showsHorizontalScrollIndicator = false
|
||||||
showsVerticalScrollIndicator = false
|
showsVerticalScrollIndicator = false
|
||||||
|
|
||||||
rebuildViews()
|
rebuildViews()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class AttachmentBannerView: UIScrollView {
|
|||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
func rebuildViews() {
|
func rebuildViews() {
|
||||||
subviews.forEach { $0.removeFromSuperview() }
|
subviews.forEach { $0.removeFromSuperview() }
|
||||||
for (index, attachment) in attachments.enumerated() {
|
for (index, attachment) in attachments.enumerated() {
|
||||||
|
|||||||
+7
-9
@@ -1,24 +1,22 @@
|
|||||||
//
|
//
|
||||||
// File.swift
|
// InputEditView+ViewModel.swift
|
||||||
// Intelligents
|
// Intelligents
|
||||||
//
|
//
|
||||||
// Created by 秋星桥 on 2024/11/18.
|
// Created by 秋星桥 on 2024/11/18.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import UIKit
|
||||||
|
|
||||||
extension InputEditView {
|
extension InputEditView {
|
||||||
class ViewModel: ObservableObject {
|
class ViewModel: ObservableObject {
|
||||||
var cancellables: Set<AnyCancellable> = []
|
var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
@Published var text: String = ""
|
@Published var text: String = ""
|
||||||
@Published var attachments: [UIImage] = []
|
@Published var attachments: [UIImage] = []
|
||||||
|
|
||||||
init() {
|
init() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
cancellables.forEach { $0.cancel() }
|
cancellables.forEach { $0.cancel() }
|
||||||
cancellables.removeAll()
|
cancellables.removeAll()
|
||||||
@@ -31,7 +29,7 @@ extension InputEditView.ViewModel: Hashable, Equatable {
|
|||||||
hasher.combine(text)
|
hasher.combine(text)
|
||||||
hasher.combine(attachments)
|
hasher.combine(attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func == (lhs: InputEditView.ViewModel, rhs: InputEditView.ViewModel) -> Bool {
|
static func == (lhs: InputEditView.ViewModel, rhs: InputEditView.ViewModel) -> Bool {
|
||||||
lhs.hashValue == rhs.hashValue
|
lhs.hashValue == rhs.hashValue
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-13
@@ -5,8 +5,8 @@
|
|||||||
// Created by 秋星桥 on 2024/11/18.
|
// Created by 秋星桥 on 2024/11/18.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import UIKit
|
||||||
|
|
||||||
class InputEditView: UIView, UITextViewDelegate {
|
class InputEditView: UIView, UITextViewDelegate {
|
||||||
let mainStack = UIStackView()
|
let mainStack = UIStackView()
|
||||||
@@ -14,14 +14,14 @@ class InputEditView: UIView, UITextViewDelegate {
|
|||||||
let textEditor = PlainTextEditView()
|
let textEditor = PlainTextEditView()
|
||||||
let placeholderLabel = UILabel()
|
let placeholderLabel = UILabel()
|
||||||
let controlBanner = TextEditControlBanner()
|
let controlBanner = TextEditControlBanner()
|
||||||
|
|
||||||
let viewModel = ViewModel()
|
let viewModel = ViewModel()
|
||||||
var placeholderText: String = "" {
|
var placeholderText: String = "" {
|
||||||
didSet {
|
didSet {
|
||||||
placeholderLabel.text = placeholderText
|
placeholderLabel.text = placeholderText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class InputEditView: UIView, UITextViewDelegate {
|
|||||||
$0.trailingAnchor.constraint(equalTo: mainStack.trailingAnchor),
|
$0.trailingAnchor.constraint(equalTo: mainStack.trailingAnchor),
|
||||||
].forEach { $0.isActive = true }
|
].forEach { $0.isActive = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
textEditor.addSubview(placeholderLabel)
|
textEditor.addSubview(placeholderLabel)
|
||||||
placeholderLabel.textColor = .label.withAlphaComponent(0.25)
|
placeholderLabel.textColor = .label.withAlphaComponent(0.25)
|
||||||
placeholderLabel.font = textEditor.font
|
placeholderLabel.font = textEditor.font
|
||||||
@@ -61,7 +61,7 @@ class InputEditView: UIView, UITextViewDelegate {
|
|||||||
placeholderLabel.trailingAnchor.constraint(equalTo: textEditor.trailingAnchor, constant: -2),
|
placeholderLabel.trailingAnchor.constraint(equalTo: textEditor.trailingAnchor, constant: -2),
|
||||||
placeholderLabel.topAnchor.constraint(equalTo: textEditor.topAnchor, constant: 0),
|
placeholderLabel.topAnchor.constraint(equalTo: textEditor.topAnchor, constant: 0),
|
||||||
].forEach { $0.isActive = true }
|
].forEach { $0.isActive = true }
|
||||||
|
|
||||||
viewModel.objectWillChange
|
viewModel.objectWillChange
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
@@ -76,26 +76,26 @@ class InputEditView: UIView, UITextViewDelegate {
|
|||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
func textViewDidChange(_ textView: UITextView) {
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
viewModel.text = textView.text
|
viewModel.text = textView.text
|
||||||
}
|
}
|
||||||
|
|
||||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
func textViewDidBeginEditing(_: UITextView) {
|
||||||
updatePlaceholderVisibility()
|
updatePlaceholderVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
func textViewDidEndEditing(_ textView: UITextView) {
|
func textViewDidEndEditing(_: UITextView) {
|
||||||
updatePlaceholderVisibility()
|
updatePlaceholderVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePlaceholderVisibility() {
|
func updatePlaceholderVisibility() {
|
||||||
let visible = viewModel.text.isEmpty && !textEditor.isFirstResponder
|
let visible = viewModel.text.isEmpty && !textEditor.isFirstResponder
|
||||||
UIView.animate(withDuration: 0.25) {
|
UIView.animate(withDuration: 0.25) {
|
||||||
self.placeholderLabel.alpha = visible ? 1 : 0
|
self.placeholderLabel.alpha = visible ? 1 : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateValues() {
|
func updateValues() {
|
||||||
UIView.animate(
|
UIView.animate(
|
||||||
withDuration: 0.5,
|
withDuration: 0.5,
|
||||||
@@ -109,7 +109,7 @@ class InputEditView: UIView, UITextViewDelegate {
|
|||||||
if attachmentsEditor.attachments != viewModel.attachments {
|
if attachmentsEditor.attachments != viewModel.attachments {
|
||||||
attachmentsEditor.attachments = viewModel.attachments
|
attachmentsEditor.attachments = viewModel.attachments
|
||||||
}
|
}
|
||||||
self.parentViewController?.view.layoutIfNeeded()
|
parentViewController?.view.layoutIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// TextEditView.swift
|
// PlainTextEditView.swift
|
||||||
// Intelligents
|
// Intelligents
|
||||||
//
|
//
|
||||||
// Created by 秋星桥 on 2024/11/18.
|
// Created by 秋星桥 on 2024/11/18.
|
||||||
|
|||||||
+2
-2
@@ -45,12 +45,12 @@ class TextEditControlBanner: UIStackView {
|
|||||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addArrangedSubview($0)
|
addArrangedSubview($0)
|
||||||
}
|
}
|
||||||
|
|
||||||
cameraButton.setImage(.init(systemName: "camera"), for: .normal)
|
cameraButton.setImage(.init(systemName: "camera"), for: .normal)
|
||||||
cameraButton.tintColor = .label
|
cameraButton.tintColor = .label
|
||||||
photoButton.setImage(.init(systemName: "photo"), for: .normal)
|
photoButton.setImage(.init(systemName: "photo"), for: .normal)
|
||||||
photoButton.tintColor = .label
|
photoButton.tintColor = .label
|
||||||
|
|
||||||
sendButton.setImage(.init(systemName: "paperplane.fill"), for: .normal)
|
sendButton.setImage(.init(systemName: "paperplane.fill"), for: .normal)
|
||||||
sendButton.tintColor = .label
|
sendButton.tintColor = .label
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -20,7 +20,6 @@ extension IntelligentsChatController {
|
|||||||
editor.textEditor.font = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
editor.textEditor.font = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
||||||
editor.placeholderText = "Summarize this article for me...".localized()
|
editor.placeholderText = "Summarize this article for me...".localized()
|
||||||
|
|
||||||
|
|
||||||
backgroundView.backgroundColor = .systemBackground
|
backgroundView.backgroundColor = .systemBackground
|
||||||
backgroundView.layer.cornerRadius = 16
|
backgroundView.layer.cornerRadius = 16
|
||||||
backgroundView.layer.shadowColor = UIColor.black.withAlphaComponent(0.25).cgColor
|
backgroundView.layer.shadowColor = UIColor.black.withAlphaComponent(0.25).cgColor
|
||||||
@@ -39,10 +38,10 @@ private extension IntelligentsChatController.InputBox {
|
|||||||
func setupLayout() {
|
func setupLayout() {
|
||||||
addSubview(backgroundView)
|
addSubview(backgroundView)
|
||||||
backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
addSubview(editor)
|
addSubview(editor)
|
||||||
editor.translatesAutoresizingMaskIntoConstraints = false
|
editor.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
let inset: CGFloat = 16
|
let inset: CGFloat = 16
|
||||||
|
|
||||||
[
|
[
|
||||||
|
|||||||
+4
-4
@@ -7,12 +7,12 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class IntelligentsChatController: UIViewController {
|
public class IntelligentsChatController: UIViewController {
|
||||||
let header = Header()
|
let header = Header()
|
||||||
let inputBox = InputBox()
|
let inputBox = InputBox()
|
||||||
let tableView = ChatTableView()
|
let tableView = ChatTableView()
|
||||||
|
|
||||||
override var title: String? {
|
override public var title: String? {
|
||||||
set {
|
set {
|
||||||
super.title = newValue
|
super.title = newValue
|
||||||
header.titleLabel.text = newValue
|
header.titleLabel.text = newValue
|
||||||
@@ -22,7 +22,7 @@ class IntelligentsChatController: UIViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
public init() {
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
title = "Chat with AI".localized()
|
title = "Chat with AI".localized()
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ class IntelligentsChatController: UIViewController {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override public func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
assert(navigationController != nil)
|
assert(navigationController != nil)
|
||||||
view.backgroundColor = .secondarySystemBackground
|
view.backgroundColor = .secondarySystemBackground
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// IntelligentsFocusApertureView+Capture.swift
|
||||||
|
// Intelligents
|
||||||
|
//
|
||||||
|
// Created by 秋星桥 on 2024/11/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension IntelligentsFocusApertureView {
|
||||||
|
func captureImageBuffer(_ targetContentView: UIView) {
|
||||||
|
let imageSize = targetContentView.frame.size
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: imageSize)
|
||||||
|
let image = renderer.image { _ in
|
||||||
|
targetContentView.drawHierarchy(
|
||||||
|
in: targetContentView.bounds,
|
||||||
|
afterScreenUpdates: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
capturedImage = image
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// IntelligentsFocusApertureView+Layout.swift
|
||||||
|
// Intelligents
|
||||||
|
//
|
||||||
|
// Created by 秋星桥 on 2024/11/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension IntelligentsFocusApertureView {
|
||||||
|
func prepareFrameLayout() {
|
||||||
|
guard let viewController = targetViewController,
|
||||||
|
let view = viewController.view
|
||||||
|
else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let safeLayout = viewController.view.safeAreaLayoutGuide
|
||||||
|
frameConstraints = [
|
||||||
|
// use safe area to layout content views
|
||||||
|
leadingAnchor.constraint(equalTo: safeLayout.leadingAnchor),
|
||||||
|
trailingAnchor.constraint(equalTo: safeLayout.trailingAnchor),
|
||||||
|
topAnchor.constraint(equalTo: safeLayout.topAnchor),
|
||||||
|
bottomAnchor.constraint(equalTo: safeLayout.bottomAnchor),
|
||||||
|
// cover all safe area so use constraints over view
|
||||||
|
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
backgroundView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareContentLayouts() {
|
||||||
|
guard let targetView else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentBeginConstraints = [
|
||||||
|
snapshotView.leftAnchor.constraint(equalTo: targetView.leftAnchor),
|
||||||
|
snapshotView.rightAnchor.constraint(equalTo: targetView.rightAnchor),
|
||||||
|
snapshotView.topAnchor.constraint(equalTo: targetView.topAnchor),
|
||||||
|
snapshotView.bottomAnchor.constraint(equalTo: targetView.bottomAnchor),
|
||||||
|
|
||||||
|
controlButtonsPanel.leftAnchor.constraint(equalTo: leftAnchor),
|
||||||
|
controlButtonsPanel.rightAnchor.constraint(equalTo: rightAnchor),
|
||||||
|
controlButtonsPanel.topAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sharedInset: CGFloat = 32
|
||||||
|
contentFinalConstraints = [
|
||||||
|
snapshotView.leftAnchor.constraint(equalTo: leftAnchor, constant: sharedInset),
|
||||||
|
snapshotView.rightAnchor.constraint(equalTo: rightAnchor, constant: -sharedInset),
|
||||||
|
snapshotView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
snapshotView.bottomAnchor.constraint(equalTo: controlButtonsPanel.topAnchor, constant: -sharedInset / 2),
|
||||||
|
|
||||||
|
controlButtonsPanel.leftAnchor.constraint(equalTo: leftAnchor, constant: sharedInset),
|
||||||
|
controlButtonsPanel.rightAnchor.constraint(equalTo: rightAnchor, constant: -sharedInset),
|
||||||
|
controlButtonsPanel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LayoutType {
|
||||||
|
case begin
|
||||||
|
case complete
|
||||||
|
}
|
||||||
|
|
||||||
|
func activateLayoutForAnimation(_ type: LayoutType) {
|
||||||
|
NSLayoutConstraint.activate(frameConstraints)
|
||||||
|
switch type {
|
||||||
|
case .begin:
|
||||||
|
NSLayoutConstraint.deactivate(contentFinalConstraints)
|
||||||
|
NSLayoutConstraint.activate(contentBeginConstraints)
|
||||||
|
|
||||||
|
snapshotView.layer.cornerRadius = 0
|
||||||
|
case .complete:
|
||||||
|
NSLayoutConstraint.deactivate(contentBeginConstraints)
|
||||||
|
NSLayoutConstraint.activate(contentFinalConstraints)
|
||||||
|
|
||||||
|
snapshotView.layer.cornerRadius = 32
|
||||||
|
}
|
||||||
|
let effectiveView = superview ?? self
|
||||||
|
effectiveView.setNeedsUpdateConstraints()
|
||||||
|
effectiveView.setNeedsLayout()
|
||||||
|
updateConstraints()
|
||||||
|
layoutIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// IntelligentsFocusApertureView+Panel.swift
|
||||||
|
// Intelligents
|
||||||
|
//
|
||||||
|
// Created by 秋星桥 on 2024/11/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension IntelligentsFocusApertureView {
|
||||||
|
class ControlButtonsPanel: UIView {
|
||||||
|
init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
backgroundColor = .red
|
||||||
|
|
||||||
|
heightAnchor.constraint(equalToConstant: 256).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
//
|
||||||
|
// IntelligentsFocusApertureView.swift
|
||||||
|
// Intelligents
|
||||||
|
//
|
||||||
|
// Created by 秋星桥 on 2024/11/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public class IntelligentsFocusApertureView: UIView {
|
||||||
|
let backgroundView = UIView()
|
||||||
|
let snapshotView = UIImageView()
|
||||||
|
let controlButtonsPanel = ControlButtonsPanel()
|
||||||
|
|
||||||
|
public var animationDuration: TimeInterval = 0.75
|
||||||
|
|
||||||
|
public internal(set) weak var targetView: UIView?
|
||||||
|
public internal(set) weak var targetViewController: UIViewController?
|
||||||
|
public internal(set) weak var capturedImage: UIImage? {
|
||||||
|
get { snapshotView.image }
|
||||||
|
set { snapshotView.image = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var frameConstraints: [NSLayoutConstraint] = []
|
||||||
|
var contentBeginConstraints: [NSLayoutConstraint] = []
|
||||||
|
var contentFinalConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
let tap = UITapGestureRecognizer(
|
||||||
|
target: self,
|
||||||
|
action: #selector(dismissFocus)
|
||||||
|
)
|
||||||
|
|
||||||
|
backgroundView.backgroundColor = .black
|
||||||
|
backgroundView.isUserInteractionEnabled = true
|
||||||
|
backgroundView.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
snapshotView.layer.masksToBounds = true
|
||||||
|
snapshotView.contentMode = .scaleAspectFill
|
||||||
|
snapshotView.isUserInteractionEnabled = true
|
||||||
|
snapshotView.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
addSubview(backgroundView)
|
||||||
|
addSubview(controlButtonsPanel)
|
||||||
|
addSubview(snapshotView)
|
||||||
|
bringSubviewToFront(snapshotView)
|
||||||
|
|
||||||
|
var views: [UIView] = [self]
|
||||||
|
while let view = views.first {
|
||||||
|
views.removeFirst()
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.subviews.forEach { views.append($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func prepareAnimationWith(
|
||||||
|
capturingTargetContentView targetContentView: UIView,
|
||||||
|
coveringRootViewController viewController: UIViewController
|
||||||
|
) {
|
||||||
|
captureImageBuffer(targetContentView)
|
||||||
|
|
||||||
|
targetView = targetContentView
|
||||||
|
targetViewController = viewController
|
||||||
|
|
||||||
|
viewController.view.addSubview(self)
|
||||||
|
|
||||||
|
prepareFrameLayout()
|
||||||
|
prepareContentLayouts()
|
||||||
|
activateLayoutForAnimation(.begin)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func executeAnimationKickIn(_ completion: @escaping () -> Void = {}) {
|
||||||
|
activateLayoutForAnimation(.begin)
|
||||||
|
isUserInteractionEnabled = false
|
||||||
|
UIView.animate(
|
||||||
|
withDuration: animationDuration,
|
||||||
|
delay: 0,
|
||||||
|
usingSpringWithDamping: 1.0,
|
||||||
|
initialSpringVelocity: 0.8
|
||||||
|
) {
|
||||||
|
self.activateLayoutForAnimation(.complete)
|
||||||
|
} completion: { _ in
|
||||||
|
self.isUserInteractionEnabled = true
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func executeAnimationDismiss(_ completion: @escaping () -> Void = {}) {
|
||||||
|
activateLayoutForAnimation(.complete)
|
||||||
|
isUserInteractionEnabled = false
|
||||||
|
UIView.animate(
|
||||||
|
withDuration: animationDuration,
|
||||||
|
delay: 0,
|
||||||
|
usingSpringWithDamping: 1.0,
|
||||||
|
initialSpringVelocity: 0.8
|
||||||
|
) {
|
||||||
|
self.activateLayoutForAnimation(.begin)
|
||||||
|
} completion: { _ in
|
||||||
|
self.isUserInteractionEnabled = true
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func dismissFocus() {
|
||||||
|
isUserInteractionEnabled = false
|
||||||
|
executeAnimationDismiss {
|
||||||
|
self.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,24 +7,24 @@ public class CookieManager: NSObject {
|
|||||||
let jar = HTTPCookieStorage.shared
|
let jar = HTTPCookieStorage.shared
|
||||||
guard let url = getServerUrl(urlString) else { return [:] }
|
guard let url = getServerUrl(urlString) else { return [:] }
|
||||||
if let cookies = jar.cookies(for: url) {
|
if let cookies = jar.cookies(for: url) {
|
||||||
for cookie in cookies {
|
for cookie in cookies {
|
||||||
cookiesMap[cookie.name] = cookie.value
|
cookiesMap[cookie.name] = cookie.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cookiesMap
|
return cookiesMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isUrlSanitized(_ urlString: String) -> Bool {
|
private func isUrlSanitized(_ urlString: String) -> Bool {
|
||||||
return urlString.hasPrefix("http://") || urlString.hasPrefix("https://")
|
urlString.hasPrefix("http://") || urlString.hasPrefix("https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getServerUrl(_ urlString: String) -> URL? {
|
public func getServerUrl(_ urlString: String) -> URL? {
|
||||||
let validUrlString = (isUrlSanitized(urlString)) ? urlString : "http://\(urlString)"
|
let validUrlString = isUrlSanitized(urlString) ? urlString : "http://\(urlString)"
|
||||||
|
|
||||||
guard let url = URL(string: validUrlString) else {
|
guard let url = URL(string: validUrlString) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import Foundation
|
|
||||||
import Capacitor
|
import Capacitor
|
||||||
|
import Foundation
|
||||||
|
|
||||||
@objc(CookiePlugin)
|
@objc(CookiePlugin)
|
||||||
public class CookiePlugin: CAPPlugin, CAPBridgedPlugin {
|
public class CookiePlugin: CAPPlugin, CAPBridgedPlugin {
|
||||||
public let identifier = "CookiePlugin"
|
public let identifier = "CookiePlugin"
|
||||||
public let jsName = "Cookie"
|
public let jsName = "Cookie"
|
||||||
public let pluginMethods: [CAPPluginMethod] = [
|
public let pluginMethods: [CAPPluginMethod] = [
|
||||||
CAPPluginMethod(name: "getCookies", returnType: CAPPluginReturnPromise)
|
CAPPluginMethod(name: "getCookies", returnType: CAPPluginReturnPromise),
|
||||||
]
|
]
|
||||||
|
|
||||||
let cookieManager = CookieManager()
|
let cookieManager = CookieManager()
|
||||||
|
|
||||||
@objc public func getCookies(_ call: CAPPluginCall) {
|
@objc public func getCookies(_ call: CAPPluginCall) {
|
||||||
guard let url = call.getString("url") else {
|
guard let url = call.getString("url") else {
|
||||||
return call.resolve([:])
|
return call.resolve([:])
|
||||||
}
|
}
|
||||||
|
|
||||||
call.resolve(cookieManager.getCookies(url))
|
call.resolve(cookieManager.getCookies(url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,105 +3,105 @@ import CryptoSwift
|
|||||||
|
|
||||||
@objc(HashcashPlugin)
|
@objc(HashcashPlugin)
|
||||||
public class HashcashPlugin: CAPPlugin, CAPBridgedPlugin {
|
public class HashcashPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||||
public let identifier = "HashcashPlugin"
|
public let identifier = "HashcashPlugin"
|
||||||
public let jsName = "Hashcash"
|
public let jsName = "Hashcash"
|
||||||
public let pluginMethods: [CAPPluginMethod] = [
|
public let pluginMethods: [CAPPluginMethod] = [
|
||||||
CAPPluginMethod(name: "hash", returnType: CAPPluginReturnPromise)
|
CAPPluginMethod(name: "hash", returnType: CAPPluginReturnPromise),
|
||||||
]
|
]
|
||||||
|
|
||||||
@objc func hash(_ call: CAPPluginCall) {
|
@objc func hash(_ call: CAPPluginCall) {
|
||||||
DispatchQueue.global(qos: .default).async {
|
DispatchQueue.global(qos: .default).async {
|
||||||
let challenge = call.getString("challenge") ?? ""
|
let challenge = call.getString("challenge") ?? ""
|
||||||
let bits = call.getInt("bits") ?? 20;
|
let bits = call.getInt("bits") ?? 20
|
||||||
call.resolve(["value": Stamp.mint(resource: challenge, bits: UInt32(bits)).format()])
|
call.resolve(["value": Stamp.mint(resource: challenge, bits: UInt32(bits)).format()])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let SALT_LENGTH = 16
|
let SALT_LENGTH = 16
|
||||||
|
|
||||||
struct Stamp {
|
struct Stamp {
|
||||||
let version: String
|
let version: String
|
||||||
let claim: UInt32
|
let claim: UInt32
|
||||||
let ts: String
|
let ts: String
|
||||||
let resource: String
|
let resource: String
|
||||||
let ext: String
|
let ext: String
|
||||||
let rand: String
|
let rand: String
|
||||||
let counter: String
|
let counter: String
|
||||||
|
|
||||||
func checkExpiration() -> Bool {
|
func checkExpiration() -> Bool {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyyMMddHHmmss"
|
dateFormatter.dateFormat = "yyyyMMddHHmmss"
|
||||||
guard let date = dateFormatter.date(from: ts) else { return false }
|
guard let date = dateFormatter.date(from: ts) else { return false }
|
||||||
return Date().addingTimeInterval(5 * 60) <= date
|
return Date().addingTimeInterval(5 * 60) <= date
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(bits: UInt32, resource: String) -> Bool {
|
||||||
|
if version == "1", bits <= claim, checkExpiration(), self.resource == resource {
|
||||||
|
let hexDigits = Int(floor(Float(claim) / 4.0))
|
||||||
|
|
||||||
|
// Check challenge
|
||||||
|
let formatted = format()
|
||||||
|
let result = formatted.data(using: .utf8)!.sha3(.sha256).compactMap { String(format: "%02x", $0) }.joined()
|
||||||
|
return result.prefix(hexDigits) == String(repeating: "0", count: hexDigits)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
func check(bits: UInt32, resource: String) -> Bool {
|
|
||||||
if version == "1" && bits <= claim && checkExpiration() && self.resource == resource {
|
func format() -> String {
|
||||||
let hexDigits = Int(floor(Float(claim) / 4.0))
|
"\(version):\(claim):\(ts):\(resource):\(ext):\(rand):\(counter)"
|
||||||
|
}
|
||||||
// Check challenge
|
|
||||||
let formatted = format()
|
static func mint(resource: String, bits: UInt32? = nil) -> Stamp {
|
||||||
let result = formatted.data(using: .utf8)!.sha3(.sha256).compactMap { String(format: "%02x", $0) }.joined()
|
let version = "1"
|
||||||
return result.prefix(hexDigits) == String(repeating: "0", count: hexDigits)
|
let now = Date()
|
||||||
} else {
|
let dateFormatter = DateFormatter()
|
||||||
return false
|
dateFormatter.dateFormat = "yyyyMMddHHmmss"
|
||||||
}
|
let ts = dateFormatter.string(from: now)
|
||||||
}
|
let bits = bits ?? 20
|
||||||
|
let rand = String((0 ..< SALT_LENGTH).map { _ in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".randomElement()! })
|
||||||
func format() -> String {
|
let challenge = "\(version):\(bits):\(ts):\(resource)::\(rand)"
|
||||||
return "\(version):\(claim):\(ts):\(resource):\(ext):\(rand):\(counter)"
|
|
||||||
}
|
let hexDigits = Int(ceil(Float(bits) / 4.0))
|
||||||
|
let zeros = String(repeating: "0", count: hexDigits)
|
||||||
static func mint(resource: String, bits: UInt32? = nil) -> Stamp {
|
var counter = 0
|
||||||
let version = "1"
|
var counterHex = ""
|
||||||
let now = Date()
|
var hasher = SHA3(variant: .sha256)
|
||||||
let dateFormatter = DateFormatter()
|
|
||||||
dateFormatter.dateFormat = "yyyyMMddHHmmss"
|
while true {
|
||||||
let ts = dateFormatter.string(from: now)
|
let toHash = "\(challenge):\(String(format: "%x", counter))"
|
||||||
let bits = bits ?? 20
|
let hashed = try! hasher.finish(withBytes: toHash.data(using: .utf8)!.bytes)
|
||||||
let rand = String((0..<SALT_LENGTH).map { _ in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".randomElement()! })
|
let result = hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||||
let challenge = "\(version):\(bits):\(ts):\(resource)::\(rand)"
|
|
||||||
|
if result.prefix(hexDigits) == zeros {
|
||||||
let hexDigits = Int(ceil(Float(bits) / 4.0))
|
counterHex = String(format: "%x", counter)
|
||||||
let zeros = String(repeating: "0", count: hexDigits)
|
break
|
||||||
var counter = 0
|
}
|
||||||
var counterHex = ""
|
counter += 1
|
||||||
var hasher = SHA3(variant: .sha256)
|
|
||||||
|
|
||||||
while true {
|
|
||||||
let toHash = "\(challenge):\(String(format: "%x", counter))"
|
|
||||||
let hashed = try! hasher.finish(withBytes: toHash.data(using: .utf8)!.bytes)
|
|
||||||
let result = hashed.compactMap { String(format: "%02x", $0) }.joined()
|
|
||||||
|
|
||||||
if result.prefix(hexDigits) == zeros {
|
|
||||||
counterHex = String(format: "%x", counter)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
counter += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return Stamp(version: version, claim: bits, ts: ts, resource: resource, ext: "", rand: rand, counter: counterHex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Stamp(version: version, claim: bits, ts: ts, resource: resource, ext: "", rand: rand, counter: counterHex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Stamp {
|
extension Stamp {
|
||||||
init?(from string: String) throws {
|
init?(from string: String) throws {
|
||||||
let parts = string.split(separator: ":")
|
let parts = string.split(separator: ":")
|
||||||
guard parts.count == 7 else {
|
guard parts.count == 7 else {
|
||||||
throw NSError(domain: "StampError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Malformed stamp, expected 7 parts, got \(parts.count)"])
|
throw NSError(domain: "StampError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Malformed stamp, expected 7 parts, got \(parts.count)"])
|
||||||
}
|
|
||||||
|
|
||||||
guard let claim = UInt32(parts[1]) else {
|
|
||||||
throw NSError(domain: "StampError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Malformed stamp"])
|
|
||||||
}
|
|
||||||
|
|
||||||
self.version = String(parts[0])
|
|
||||||
self.claim = claim
|
|
||||||
self.ts = String(parts[2])
|
|
||||||
self.resource = String(parts[3])
|
|
||||||
self.ext = String(parts[4])
|
|
||||||
self.rand = String(parts[5])
|
|
||||||
self.counter = String(parts[6])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let claim = UInt32(parts[1]) else {
|
||||||
|
throw NSError(domain: "StampError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Malformed stamp"])
|
||||||
|
}
|
||||||
|
|
||||||
|
version = String(parts[0])
|
||||||
|
self.claim = claim
|
||||||
|
ts = String(parts[2])
|
||||||
|
resource = String(parts[3])
|
||||||
|
ext = String(parts[4])
|
||||||
|
rand = String(parts[5])
|
||||||
|
counter = String(parts[6])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Foundation
|
|
||||||
import Capacitor
|
import Capacitor
|
||||||
|
import Foundation
|
||||||
|
|
||||||
@objc(IntelligentsPlugin)
|
@objc(IntelligentsPlugin)
|
||||||
public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||||
@@ -7,27 +7,29 @@ public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
|||||||
public let jsName = "Intelligents"
|
public let jsName = "Intelligents"
|
||||||
public let pluginMethods: [CAPPluginMethod] = [
|
public let pluginMethods: [CAPPluginMethod] = [
|
||||||
CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise),
|
CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||||
CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise)
|
CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||||
]
|
]
|
||||||
public let ui: UIViewController
|
public private(set) weak var representController: UIViewController?
|
||||||
|
|
||||||
init(ui: UIViewController) {
|
init(representController: UIViewController) {
|
||||||
self.ui = ui
|
self.representController = representController
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
representController = nil
|
||||||
|
}
|
||||||
|
|
||||||
@objc public func presentIntelligentsButton(_ call: CAPPluginCall) {
|
@objc public func presentIntelligentsButton(_ call: CAPPluginCall) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.ui.presentIntelligentsButton()
|
self.representController?.presentIntelligentsButton()
|
||||||
print("!!!!!!!!!!!!present")
|
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func dismissIntelligentsButton(_ call: CAPPluginCall) {
|
@objc public func dismissIntelligentsButton(_ call: CAPPluginCall) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.ui.dismissIntelligentsButton()
|
self.representController?.dismissIntelligentsButton()
|
||||||
print("!!!!!!!!!!!!dismiss")
|
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,28 +9,28 @@ import UIKit
|
|||||||
|
|
||||||
@objc
|
@objc
|
||||||
class RootViewController: UINavigationController {
|
class RootViewController: UINavigationController {
|
||||||
override init(rootViewController: UIViewController) {
|
override init(rootViewController _: UIViewController) {
|
||||||
fatalError() // "you are not allowed to call this"
|
fatalError() // "you are not allowed to call this"
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) {
|
override init(navigationBarClass _: AnyClass?, toolbarClass _: AnyClass?) {
|
||||||
fatalError() // "you are not allowed to call this"
|
fatalError() // "you are not allowed to call this"
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
commitInit()
|
commitInit()
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
override init(nibName _: String?, bundle _: Bundle?) {
|
||||||
fatalError() // "you are not allowed to call this"
|
fatalError() // "you are not allowed to call this"
|
||||||
}
|
}
|
||||||
|
|
||||||
func commitInit() {
|
func commitInit() {
|
||||||
assert(viewControllers.isEmpty)
|
assert(viewControllers.isEmpty)
|
||||||
viewControllers = [AFFiNEViewController()]
|
viewControllers = [AFFiNEViewController()]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
view.backgroundColor = .systemBackground
|
view.backgroundColor = .systemBackground
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: 1b0d3fe81862c0e9ce712ddd0c5a0accd0097698
|
PODFILE CHECKSUM: 1b0d3fe81862c0e9ce712ddd0c5a0accd0097698
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
Reference in New Issue
Block a user