feat(ios): intelligent Switch Markdown View & Ephemeral Action View (#9823)

Co-authored-by: EYHN <cneyhn@gmail.com>
This commit is contained in:
Lakr
2025-03-24 17:47:42 +08:00
committed by GitHub
parent 4d3eee3bad
commit 447b23f25f
192 changed files with 43960 additions and 980 deletions

View File

@@ -0,0 +1,127 @@
//
// File.swift
// App
//
// Created by on 2025/1/8.
//
import UIKit
import Intelligents
import ChidoriMenu
extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate {
func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
guard let webView else {
assertionFailure() // ? wdym ?
return
}
button.beginProgress()
let group = DispatchGroup()
group.enter()
webView.evaluateScript(.getCurrentServerBaseUrl) { result in
self.baseUrl = result as? String
print("[*] setting baseUrl: \(self.baseUrl ?? "")")
group.leave()
}
group.enter()
webView.evaluateScript(.getCurrentDocId) { result in
self.documentID = result as? String
print("[*] setting documentID: \(self.documentID ?? "")")
group.leave()
}
group.enter()
webView.evaluateScript(.getCurrentWorkspaceId) { result in
self.workspaceID = result as? String
print("[*] setting workspaceID: \(self.workspaceID ?? "")")
group.leave()
}
group.enter()
webView.evaluateScript(.getCurrentDocContentInMarkdown) { input in
self.documentContent = input as? String
print("[*] setting documentContent: \(self.documentContent?.count ?? 0) chars")
group.leave()
}
DispatchQueue.global().asyncAfter(deadline: .now()) {
group.wait()
DispatchQueue.main.async {
button.stopProgress()
webView.resignFirstResponder()
self.openIntelligentsSheet()
}
}
}
@discardableResult
func openIntelligentsSheet() -> IntelligentsFocusApertureView? {
dismissIntelligentsButton()
view.resignFirstResponder()
// stop scroll on webview
if let contentOffset = webView?.scrollView.contentOffset {
webView?.scrollView.contentOffset = contentOffset
}
let focus = IntelligentsFocusApertureView()
focus.prepareAnimationWith(
capturingTargetContentView: webView ?? .init(),
coveringRootViewController: self
)
focus.delegate = self
focus.executeAnimationKickIn()
dismissIntelligentsButton()
return focus
}
func openSimpleChat() {
let targetController = IntelligentsChatController()
presentIntoCurrentContext(withTargetController: targetController)
}
func focusApertureRequestAction(
from view: IntelligentsFocusApertureView,
actionType: IntelligentsFocusApertureViewActionType
) {
switch actionType {
case .translateTo:
var actions: [UIAction] = []
for lang in IntelligentsEphemeralActionController.EphemeralAction.Language.allCases {
actions.append(.init(title: lang.rawValue) { [weak self] _ in
guard let self else { return }
let controller = IntelligentsEphemeralActionController(
action: .translate(to: lang)
)
controller.workspaceID = workspaceID ?? ""
controller.documentID = documentID ?? ""
controller.documentContent = documentContent ?? ""
controller.configure(previewImage: view.capturedImage ?? .init())
presentIntoCurrentContext(withTargetController: controller)
})
}
view.present(menu: .init(children: actions)) { menu in
menu.overrideUserInterfaceStyle = .dark
}
case .summary:
let controller = IntelligentsEphemeralActionController(
action: .summarize
)
controller.configure(previewImage: view.capturedImage ?? .init())
controller.workspaceID = workspaceID ?? ""
controller.documentID = documentID ?? ""
controller.documentContent = documentContent ?? ""
presentIntoCurrentContext(withTargetController: controller)
case .chatWithAI:
let controller = IntelligentsChatController()
controller.metadata[.documentID] = documentID
controller.metadata[.workspaceID] = workspaceID
controller.metadata[.content] = documentContent
presentIntoCurrentContext(withTargetController: controller)
case .dismiss:
presentIntelligentsButton()
}
}
}

View File

@@ -3,6 +3,13 @@ import Intelligents
import UIKit
class AFFiNEViewController: CAPBridgeViewController {
var baseUrl: String? {
didSet { Intelligents.setUpstreamEndpoint(baseUrl ?? "") }
}
var documentID: String?
var workspaceID: String?
var documentContent: String?
override func viewDidLoad() {
super.viewDidLoad()
webView?.allowsBackForwardNavigationGestures = true
@@ -39,84 +46,6 @@ class AFFiNEViewController: CAPBridgeViewController {
super.viewDidAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
}
extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate {
func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
guard let webView else {
assertionFailure() // ? wdym ?
return
}
button.beginProgress()
let upstreamReaderScript = "window.getCurrentServerBaseUrl();"
webView.evaluateJavaScript(upstreamReaderScript) { result, _ in
if let baseUrl = result as? String {
Intelligents.setUpstreamEndpoint(baseUrl)
}
let script = "return await window.getCurrentDocContentInMarkdown();"
webView.callAsyncJavaScript(
script,
arguments: [:],
in: nil,
in: .page
) { result in
button.stopProgress()
webView.resignFirstResponder()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if case let .success(content) = result,
let res = content as? String
{
print("[*] \(self) received document with \(res.count) characters")
self.openIntelligentsSheet(withContext: res)
} else {
self.openSimpleChat()
}
}
}
}
}
func openIntelligentsSheet(withContext context: String) {
guard let view = webView?.subviews.first else {
assertionFailure()
return
}
assert(view is UIScrollView)
_ = context
let focus = IntelligentsFocusApertureView()
focus.prepareAnimationWith(
capturingTargetContentView: view,
coveringRootViewController: self
)
focus.delegate = self
focus.executeAnimationKickIn()
dismissIntelligentsButton()
}
func openSimpleChat() {
let targetController = IntelligentsChatController()
presentIntoCurrentContext(withTargetController: targetController)
}
func focusApertureRequestAction(actionType: IntelligentsFocusApertureViewActionType) {
switch actionType {
case .translateTo:
fatalError("not implemented")
case .summary:
fatalError("not implemented")
case .chatWithAI:
let controller = IntelligentsChatController()
presentIntoCurrentContext(withTargetController: controller)
case .dismiss:
presentIntelligentsButton()
}
}
}

View File

@@ -0,0 +1,47 @@
//
// ApplicationBridgedWindowScript.swift
// App
//
// Created by on 2025/1/8.
//
import Foundation
import WebKit
enum ApplicationBridgedWindowScript: String {
case getCurrentDocContentInMarkdown = "return await window.getCurrentDocContentInMarkdown();"
case getCurrentServerBaseUrl = "window.getCurrentServerBaseUrl()"
case getCurrentWorkspaceId = "window.getCurrentWorkspaceId();"
case getCurrentDocId = "window.getCurrentDocId();"
var requiresAsyncContext: Bool {
switch self {
case .getCurrentDocContentInMarkdown: return true
default: return false
}
}
}
extension WKWebView {
func evaluateScript(_ script: ApplicationBridgedWindowScript, callback: @escaping (Any?) -> ()) {
if script.requiresAsyncContext {
callAsyncJavaScript(
script.rawValue,
arguments: [:],
in: nil,
in: .page
) { result in
switch result {
case .success(let input):
callback(input)
case .failure:
callback(nil)
}
}
} else {
evaluateJavaScript(script.rawValue) { output, _ in callback(output) }
}
}
}

View File

@@ -497,7 +497,7 @@ fileprivate struct FfiConverterString: FfiConverter {
public protocol DocStoragePoolProtocol: AnyObject {
public protocol DocStoragePoolProtocol: AnyObject, Sendable {
func clearClocks(universalId: String) async throws
@@ -573,6 +573,9 @@ open class DocStoragePool: DocStoragePoolProtocol, @unchecked Sendable {
// TODO: We'd like this to be `private` but for Swifty reasons,
// we can't implement `FfiConverter` without making this `required` and we can't
// make it `required` without making it `public`.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
self.pointer = pointer
}
@@ -621,7 +624,7 @@ open func clearClocks(universalId: String)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -641,7 +644,7 @@ open func connect(universalId: String, path: String)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -658,7 +661,7 @@ open func deleteBlob(universalId: String, key: String, permanently: Bool)async t
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -675,7 +678,7 @@ open func deleteDoc(universalId: String, docId: String)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -692,7 +695,7 @@ open func disconnect(universalId: String)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -709,7 +712,7 @@ open func getBlob(universalId: String, key: String)async throws -> Blob? {
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionTypeBlob.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -726,7 +729,7 @@ open func getBlobUploadedAt(universalId: String, peer: String, blobId: String)as
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionInt64.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -743,7 +746,7 @@ open func getDocClock(universalId: String, docId: String)async throws -> DocClo
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -760,7 +763,7 @@ open func getDocClocks(universalId: String, after: Int64?)async throws -> [DocC
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -777,7 +780,7 @@ open func getDocSnapshot(universalId: String, docId: String)async throws -> Doc
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionTypeDocRecord.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -794,7 +797,7 @@ open func getDocUpdates(universalId: String, docId: String)async throws -> [Doc
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeDocUpdate.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -811,7 +814,7 @@ open func getPeerPulledRemoteClock(universalId: String, peer: String, docId: Str
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -828,7 +831,7 @@ open func getPeerPulledRemoteClocks(universalId: String, peer: String)async thro
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -845,7 +848,7 @@ open func getPeerPushedClock(universalId: String, peer: String, docId: String)as
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -862,7 +865,7 @@ open func getPeerPushedClocks(universalId: String, peer: String)async throws ->
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -879,7 +882,7 @@ open func getPeerRemoteClock(universalId: String, peer: String, docId: String)as
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterOptionTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -896,7 +899,7 @@ open func getPeerRemoteClocks(universalId: String, peer: String)async throws ->
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeDocClock.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -913,7 +916,7 @@ open func listBlobs(universalId: String)async throws -> [ListedBlob] {
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeListedBlob.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -930,7 +933,7 @@ open func markUpdatesMerged(universalId: String, docId: String, updates: [Int64]
completeFunc: ffi_affine_mobile_native_rust_future_complete_u32,
freeFunc: ffi_affine_mobile_native_rust_future_free_u32,
liftFunc: FfiConverterUInt32.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -947,7 +950,7 @@ open func pushUpdate(universalId: String, docId: String, update: String)async th
completeFunc: ffi_affine_mobile_native_rust_future_complete_i64,
freeFunc: ffi_affine_mobile_native_rust_future_free_i64,
liftFunc: FfiConverterInt64.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -964,7 +967,7 @@ open func releaseBlobs(universalId: String)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -981,7 +984,7 @@ open func setBlob(universalId: String, blob: SetBlob)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -998,7 +1001,7 @@ open func setBlobUploadedAt(universalId: String, peer: String, blobId: String, u
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -1015,7 +1018,7 @@ open func setDocSnapshot(universalId: String, snapshot: DocRecord)async throws
completeFunc: ffi_affine_mobile_native_rust_future_complete_i8,
freeFunc: ffi_affine_mobile_native_rust_future_free_i8,
liftFunc: FfiConverterBool.lift,
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -1032,7 +1035,7 @@ open func setPeerPulledRemoteClock(universalId: String, peer: String, docId: Str
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -1049,7 +1052,7 @@ open func setPeerPushedClock(universalId: String, peer: String, docId: String, c
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -1066,7 +1069,7 @@ open func setPeerRemoteClock(universalId: String, peer: String, docId: String, c
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -1083,7 +1086,7 @@ open func setSpaceId(universalId: String, spaceId: String)async throws {
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
errorHandler: FfiConverterTypeUniffiError_lift
)
}
@@ -1627,7 +1630,7 @@ public func FfiConverterTypeSetBlob_lower(_ value: SetBlob) -> RustBuffer {
}
public enum UniffiError {
public enum UniffiError: Swift.Error {