Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
172 lines
5.6 KiB
Swift
172 lines
5.6 KiB
Swift
import Capacitor
|
||
import Intelligents
|
||
import UIKit
|
||
import WebKit
|
||
|
||
class AFFiNEViewController: CAPBridgeViewController, UIScrollViewDelegate, AffineThemeConfigurable {
|
||
var intelligentsButton: IntelligentsButton?
|
||
var appThemeUserInterfaceStyle: UIUserInterfaceStyle = .unspecified {
|
||
didSet {
|
||
overrideUserInterfaceStyle = appThemeUserInterfaceStyle
|
||
}
|
||
}
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
webView?.allowsBackForwardNavigationGestures = false
|
||
navigationController?.navigationBar.isHidden = true
|
||
extendedLayoutIncludesOpaqueBars = false
|
||
edgesForExtendedLayout = []
|
||
|
||
// Disable WKWebView scrollView zoom/bounce to prevent conflict with edgeless canvas gestures
|
||
webView?.scrollView.minimumZoomScale = 1.0
|
||
webView?.scrollView.maximumZoomScale = 1.0
|
||
webView?.scrollView.bouncesZoom = false
|
||
webView?.scrollView.bounces = false
|
||
webView?.scrollView.pinchGestureRecognizer?.isEnabled = false
|
||
webView?.scrollView.delegate = self
|
||
|
||
// Inject viewport meta to prevent WKWebView smart zoom
|
||
let viewportScript = """
|
||
(function() {
|
||
function setViewport() {
|
||
var meta = document.querySelector('meta[name="viewport"]');
|
||
if (!meta) {
|
||
meta = document.createElement('meta');
|
||
meta.name = 'viewport';
|
||
(document.head || document.documentElement).appendChild(meta);
|
||
}
|
||
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
|
||
}
|
||
if (document.head) {
|
||
setViewport();
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', setViewport);
|
||
}
|
||
})();
|
||
"""
|
||
webView?.configuration.userContentController.addUserScript(
|
||
WKUserScript(source: viewportScript, injectionTime: .atDocumentStart, forMainFrameOnly: true)
|
||
)
|
||
|
||
let intelligentsButton = installIntelligentsButton()
|
||
intelligentsButton.delegate = self
|
||
self.intelligentsButton = intelligentsButton
|
||
dismissIntelligentsButton()
|
||
}
|
||
|
||
override func didReceiveMemoryWarning() {
|
||
super.didReceiveMemoryWarning()
|
||
webView?.evaluateJavaScript("window.dispatchEvent(new Event('affine:memory-pressure'))")
|
||
}
|
||
|
||
override func capacitorDidLoad() {
|
||
let plugins: [CAPPlugin] = [
|
||
AffineThemePlugin(associatedController: self),
|
||
AuthPlugin(),
|
||
CookiePlugin(),
|
||
HashcashPlugin(),
|
||
ImagePickerPlugin(associatedController: self),
|
||
NavigationGesturePlugin(),
|
||
NbStorePlugin(),
|
||
PayWallPlugin(associatedController: self),
|
||
PreviewPlugin(),
|
||
ShareInboxPlugin(),
|
||
]
|
||
plugins.forEach { bridge?.registerPluginInstance($0) }
|
||
}
|
||
|
||
private static let intelligentsButtonRefreshInterval: TimeInterval = 0.5
|
||
|
||
private var intelligentsButtonTimer: Timer?
|
||
private var isCheckingIntelligentEligibility = false
|
||
|
||
override func viewDidAppear(_ animated: Bool) {
|
||
super.viewDidAppear(animated)
|
||
IntelligentContext.shared.webView = webView
|
||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||
checkEligibilityOfIntelligent()
|
||
let timer = Timer.scheduledTimer(withTimeInterval: Self.intelligentsButtonRefreshInterval, repeats: true) { [weak self] _ in
|
||
self?.checkEligibilityOfIntelligent()
|
||
}
|
||
intelligentsButtonTimer = timer
|
||
RunLoop.main.add(timer, forMode: .common)
|
||
processShareInboxIfNeeded()
|
||
}
|
||
|
||
func processShareInboxIfNeeded() {
|
||
guard let webView, !ShareInboxStore.shared.pendingItems().isEmpty else { return }
|
||
webView.evaluateJavaScript(
|
||
"window.dispatchEvent(new Event('affine:share-inbox'))"
|
||
)
|
||
}
|
||
|
||
private func checkEligibilityOfIntelligent() {
|
||
guard !isCheckingIntelligentEligibility else { return }
|
||
assert(intelligentsButton != nil)
|
||
guard let webView else {
|
||
if intelligentsButton?.isHidden == false {
|
||
dismissIntelligentsButton()
|
||
}
|
||
return
|
||
}
|
||
|
||
isCheckingIntelligentEligibility = true
|
||
Task { @MainActor [weak self, webView] in
|
||
guard let self else { return }
|
||
|
||
guard await PaywallAuthGuard.currentUserIdentifier(in: webView) != nil else {
|
||
if self.intelligentsButton?.isHidden == false {
|
||
self.dismissIntelligentsButton()
|
||
}
|
||
self.isCheckingIntelligentEligibility = false
|
||
return
|
||
}
|
||
|
||
guard self.intelligentsButton?.isHidden ?? false else {
|
||
self.isCheckingIntelligentEligibility = false
|
||
return
|
||
}
|
||
|
||
IntelligentContext.shared.webView = webView
|
||
IntelligentContext.shared.preparePresent(createSession: false) { [weak self] result in
|
||
DispatchQueue.main.async {
|
||
guard let self else { return }
|
||
defer { self.isCheckingIntelligentEligibility = false }
|
||
switch result {
|
||
case .failure: break
|
||
case .success:
|
||
self.presentIntelligentsButton()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
override func viewDidDisappear(_ animated: Bool) {
|
||
super.viewDidDisappear(animated)
|
||
intelligentsButtonTimer?.invalidate()
|
||
}
|
||
|
||
// MARK: - UIScrollViewDelegate
|
||
|
||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||
return nil
|
||
}
|
||
|
||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||
scrollView.zoomScale = 1.0
|
||
}
|
||
|
||
// MARK: - Web Content Process Crash Recovery
|
||
|
||
// NOTE: Capacitor's CAPBridgeViewController owns the WKWebView
|
||
// navigationDelegate (it assigns its own WebViewDelegationHandler), so this
|
||
// override is NOT called in practice — Capacitor's handler logs
|
||
// "⚡️ WebView process terminated" and reloads instead. Kept as defensive
|
||
// fallback, matching the prior baseline behavior.
|
||
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
|
||
webView.reload()
|
||
}
|
||
}
|