mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
fix: slow rendering after searching and back to home
This commit is contained in:
@@ -31,6 +31,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
/// Temporary snapshot image for smooth pop transitions.
|
||||
private var popSnapshotView: UIView?
|
||||
|
||||
// Each stack has its own native VC stack, just like paths.
|
||||
private var stackViewControllerStacks: [String: [UIViewController]] = [:]
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// MARK: Helpers
|
||||
// ---------------------------------------------------------
|
||||
@@ -40,14 +43,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
return raw
|
||||
}
|
||||
|
||||
private func debugLogStacks(_ label: String) {
|
||||
#if DEBUG
|
||||
print("🧭 [\(label)] activeStackId=\(activeStackId)")
|
||||
print(" pathStack=\(pathStack)")
|
||||
print(" stackPathStacks=\(stackPathStacks)")
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Returns the current native path stack for a given logical stack id,
|
||||
/// or initialises a sensible default if none exists yet.
|
||||
private func paths(for stackId: String) -> [String] {
|
||||
@@ -72,6 +67,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
}
|
||||
}
|
||||
|
||||
private func setViewControllers(_ vcs: [UIViewController], for stackId: String) {
|
||||
stackViewControllerStacks[stackId] = vcs
|
||||
}
|
||||
|
||||
private func viewControllers(for stackId: String) -> [UIViewController] {
|
||||
stackViewControllerStacks[stackId] ?? []
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// MARK: UIApplication lifecycle
|
||||
// ---------------------------------------------------------
|
||||
@@ -198,7 +201,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
SharedWebViewController.instance.clearPlaceholder()
|
||||
SharedWebViewController.instance.attach(to: vc)
|
||||
|
||||
debugLogStacks("emptyNavStack")
|
||||
}
|
||||
|
||||
private func pushIfNeeded(path: String, animated: Bool) {
|
||||
@@ -216,7 +218,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
|
||||
nav.pushViewController(vc, animated: animated)
|
||||
|
||||
debugLogStacks("pushIfNeeded")
|
||||
}
|
||||
|
||||
private func replaceTop(path: String) {
|
||||
@@ -236,7 +237,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
}
|
||||
nav.setViewControllers(stack, animated: false)
|
||||
|
||||
debugLogStacks("replaceTop")
|
||||
}
|
||||
|
||||
private func popIfNeeded(animated: Bool) {
|
||||
@@ -246,8 +246,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
_ = pathStack.popLast()
|
||||
setPaths(pathStack, for: activeStackId)
|
||||
nav.popViewController(animated: animated)
|
||||
|
||||
debugLogStacks("popIfNeeded")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,12 +272,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
vcs.count < pathStack.count
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
print("🧭 willShow — isPop=\(isPop)")
|
||||
print(" toVC=\(toVC.targetPath) fromVC=\(String(describing: fromVC?.targetPath))")
|
||||
debugLogStacks("willShow")
|
||||
#endif
|
||||
|
||||
if isPop {
|
||||
// -----------------------------
|
||||
// POP — update per-stack pathStack, then notify JS.
|
||||
@@ -322,10 +314,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
// 🔑 DO NOT call webView.goBack().
|
||||
// Tell JS explicitly that native popped.
|
||||
self.ignoreRoutePopCount += 1
|
||||
#if DEBUG
|
||||
print("⬅️ Native POP completed, notifying JS via onNativePop(), ignoreRoutePopCount=\(self.ignoreRoutePopCount)")
|
||||
debugLogStacks("after native-pop pathStack update")
|
||||
#endif
|
||||
|
||||
if let bridge = SharedWebViewController.instance.bridgeController.bridge {
|
||||
let js = "window.LogseqNative && window.LogseqNative.onNativePop && window.LogseqNative.onNativePop();"
|
||||
@@ -395,9 +383,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
|
||||
private func observeRouteChanges() {
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: UILocalPlugin.routeChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
forName: UILocalPlugin.routeChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self else { return }
|
||||
guard let nav = self.navController else { return }
|
||||
@@ -408,40 +396,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
let stackId = (notification.userInfo?["stack"] as? String) ?? "home"
|
||||
let previousStackId = self.activeStackId
|
||||
|
||||
#if DEBUG
|
||||
print("📡 routeDidChange from JS → native")
|
||||
print(" stackId=\(stackId) navigationType=\(navigationType) path=\(path)")
|
||||
debugLogStacks("before observeRouteChanges")
|
||||
#endif
|
||||
// 🚫 Fast-path: ignore duplicate replace for same stack/path
|
||||
if stackId == self.activeStackId,
|
||||
navigationType == "replace",
|
||||
path == self.pathStack.last {
|
||||
return
|
||||
}
|
||||
|
||||
// ⚡️ Fast-path: cancel search → home root.
|
||||
// We do NOT rebuild nav stack and we do NOT reattach the WebView.
|
||||
// JS will just navigate the existing shared WKWebView to "/".
|
||||
if previousStackId == "search",
|
||||
stackId == "home"{
|
||||
|
||||
// Just update bookkeeping so future home pushes/pop work correctly.
|
||||
self.activeStackId = "home"
|
||||
self.setPaths(["/"], for: "home")
|
||||
self.setPaths(["/__stack__/search"], for: "search")
|
||||
|
||||
nav.setViewControllers([], animated: false)
|
||||
self.setViewControllers([], for: "home")
|
||||
|
||||
// 👈 Do NOTHING to nav.viewControllers or SharedWebViewController here.
|
||||
return
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 1️⃣ Stack switch: home ↔ capture ↔ goto ...
|
||||
// 1️⃣ Stack switch: home ↔ search ↔ capture...
|
||||
// ============================================
|
||||
if stackId != self.activeStackId {
|
||||
// Save current native stack paths; drop stale search stack when leaving it.
|
||||
if previousStackId == "search", stackId != "search" {
|
||||
self.setPaths(["/__stack__/search"], for: "search")
|
||||
} else {
|
||||
self.setPaths(self.pathStack, for: previousStackId)
|
||||
}
|
||||
self.setPaths(self.pathStack, for: previousStackId)
|
||||
|
||||
// Load (or create) new stack's paths
|
||||
// Load saved paths for target stack
|
||||
var newPaths = self.paths(for: stackId)
|
||||
|
||||
// Ensure the top of the stack matches the path sent by JS
|
||||
if let last = newPaths.last, last != path {
|
||||
if newPaths.isEmpty {
|
||||
newPaths = [path]
|
||||
} else {
|
||||
newPaths[newPaths.count - 1] = path
|
||||
}
|
||||
// 🔑 Special rules for shaping the new stack
|
||||
if stackId == "home", path == "/" {
|
||||
// 👉 ALWAYS reset home to a single root VC.
|
||||
newPaths = ["/"]
|
||||
} else if newPaths.isEmpty {
|
||||
// First time visiting this stack
|
||||
newPaths = [path]
|
||||
} else if let last = newPaths.last, last != path {
|
||||
// Same history, but different top path → align the top.
|
||||
newPaths[newPaths.count - 1] = path
|
||||
}
|
||||
|
||||
self.activeStackId = stackId
|
||||
self.pathStack = newPaths
|
||||
self.setPaths(newPaths, for: stackId)
|
||||
|
||||
// Rebuild the UINavigationController's stack from these paths
|
||||
// Rebuild native stack for these paths
|
||||
var vcs: [UIViewController] = []
|
||||
for (idx, p) in newPaths.enumerated() {
|
||||
let vc = NativePageViewController(path: p, push: idx > 0)
|
||||
@@ -449,24 +454,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
}
|
||||
|
||||
nav.setViewControllers(vcs, animated: false)
|
||||
self.setViewControllers(vcs, for: stackId)
|
||||
|
||||
if let lastVC = vcs.last as? NativePageViewController {
|
||||
SharedWebViewController.instance.attach(to: lastVC)
|
||||
SharedWebViewController.instance.clearPlaceholder()
|
||||
// Defer & avoid redundant attach.
|
||||
DispatchQueue.main.async {
|
||||
if let bridge = SharedWebViewController.instance.bridgeController.bridge,
|
||||
let webView = bridge.webView,
|
||||
webView.isDescendant(of: lastVC.view) {
|
||||
} else {
|
||||
SharedWebViewController.instance.attach(to: lastVC)
|
||||
}
|
||||
SharedWebViewController.instance.clearPlaceholder()
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
print("🔀 STACK SWITCH to \(stackId)")
|
||||
debugLogStacks("after stack switch")
|
||||
#endif
|
||||
|
||||
// For stacks like "capture", default paths are ["__/stack__/capture"],
|
||||
// so they get a single VC and no back button.
|
||||
return
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 2️⃣ Navigation *within* the active stack
|
||||
// 2️⃣ Navigation *within* active stack
|
||||
// ============================================
|
||||
switch navigationType {
|
||||
case "reset":
|
||||
@@ -478,10 +485,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
case "pop":
|
||||
if self.ignoreRoutePopCount > 0 {
|
||||
self.ignoreRoutePopCount -= 1
|
||||
#if DEBUG
|
||||
print("🙈 ignoring JS pop (ignoreRoutePopCount→\(self.ignoreRoutePopCount))")
|
||||
debugLogStacks("after ignore JS pop")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
if self.pathStack.count > 1 {
|
||||
@@ -491,12 +494,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
|
||||
default:
|
||||
self.pushIfNeeded(path: path, animated: true)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
debugLogStacks("after observeRouteChanges switch")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
||||
@@ -320,7 +320,6 @@ private struct SearchTabHost26: View {
|
||||
wasSearching = false
|
||||
selectedTab.wrappedValue = .content(0)
|
||||
store.selectedId = firstId
|
||||
LiquidTabsPlugin.shared?.notifyTabSelected(id: firstId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
route-match (state/sub :route-match)]
|
||||
[:main#app-container-wrapper.ls-fold-button-on-right
|
||||
[:div#app-container {:class (when show-popup? "invisible")}
|
||||
[:div#main-container.flex.flex-1.overflow-x-hidden.py-4
|
||||
[:div#main-container.flex.flex-1.overflow-x-hidden
|
||||
(app current-repo route-match)]]
|
||||
(when show-popup?
|
||||
[:div.ls-layer
|
||||
|
||||
@@ -16,30 +16,9 @@
|
||||
(defonce ^:private pending-navigation (atom nil))
|
||||
(defonce ^:private hooks-installed? (atom false))
|
||||
|
||||
;; --- DEBUG toggle ---
|
||||
(def ^:private debug-nav? true)
|
||||
|
||||
(defn- dbg [tag & args]
|
||||
(when debug-nav?
|
||||
(let [payload (cond
|
||||
;; one map argument → use it directly
|
||||
(and (= 1 (count args))
|
||||
(map? (first args)))
|
||||
(first args)
|
||||
|
||||
;; even number of args → treat as k/v pairs
|
||||
(even? (count args))
|
||||
(apply hash-map args)
|
||||
|
||||
;; odd / weird → just log the raw args
|
||||
:else
|
||||
{:args args})]
|
||||
(log/info tag payload))))
|
||||
|
||||
;; Track whether the latest change came from a native back gesture / popstate.
|
||||
(.addEventListener js/window "popstate" (fn [_]
|
||||
(reset! navigation-source :pop)
|
||||
(dbg :nav/popstate {:source :popstate})))
|
||||
(reset! navigation-source :pop)))
|
||||
|
||||
(defn current-stack
|
||||
[]
|
||||
@@ -48,7 +27,6 @@
|
||||
(defn set-current-stack!
|
||||
[stack]
|
||||
(when (some? stack)
|
||||
(dbg :nav/set-current-stack {:from @active-stack :to stack})
|
||||
(reset! active-stack stack)))
|
||||
|
||||
(defn- strip-fragment [href]
|
||||
@@ -77,7 +55,6 @@
|
||||
(defn- record-navigation-intent!
|
||||
[{:keys [type stack]}]
|
||||
(let [stack (or stack @active-stack primary-stack)]
|
||||
(dbg :nav/record-intent {:type type :stack stack})
|
||||
(reset! pending-navigation {:type type
|
||||
:stack stack})))
|
||||
|
||||
@@ -91,7 +68,6 @@
|
||||
([k params query]
|
||||
(record-navigation-intent! {:type :push
|
||||
:stack @active-stack})
|
||||
(dbg :nav/push-state {:name k :params params :query query :stack @active-stack})
|
||||
(orig-push-state k params query)))
|
||||
|
||||
(defonce orig-replace-state rfe/replace-state)
|
||||
@@ -103,7 +79,6 @@
|
||||
([k params query]
|
||||
(record-navigation-intent! {:type :replace
|
||||
:stack @active-stack})
|
||||
(dbg :nav/replace-state {:name k :params params :query query :stack @active-stack})
|
||||
(orig-replace-state k params query)))
|
||||
|
||||
(defn install-navigation-hooks!
|
||||
@@ -111,7 +86,6 @@
|
||||
Also tags navigation with the active stack so native can keep per-stack history."
|
||||
[]
|
||||
(when (compare-and-set! hooks-installed? false true)
|
||||
(dbg :nav/hooks-installed {})
|
||||
(set! rfe/push-state push-state)
|
||||
(set! rfe/replace-state replace-state)))
|
||||
|
||||
@@ -132,14 +106,6 @@
|
||||
[stack]
|
||||
(-> @stack-history (get stack) :history last))
|
||||
|
||||
;; --- DEBUG: watch stack-history changes ---
|
||||
(add-watch stack-history ::stack-history-debug
|
||||
(fn [_ _ old new]
|
||||
(when debug-nav?
|
||||
(dbg :nav/stack-history
|
||||
:old (into {} (for [[k v] old] [k (mapv :path (:history v))]))
|
||||
:new (into {} (for [[k v] new] [k (mapv :path (:history v))]))))))
|
||||
|
||||
(defn- remember-route!
|
||||
[stack nav-type route path route-match]
|
||||
(when stack
|
||||
@@ -160,12 +126,6 @@
|
||||
(conj history entry))
|
||||
history)))]
|
||||
(when entry
|
||||
(dbg :nav/remember-route
|
||||
:stack stack
|
||||
:nav-type nav-type
|
||||
:path path
|
||||
:route-to (or (get-in route [:to])
|
||||
(get-in route-match [:data :name])))
|
||||
(swap! stack-history update stack (fn [{:keys [history] :as st}]
|
||||
{:history (update-history history)}))
|
||||
(swap! initialised-stacks assoc stack true)))))
|
||||
@@ -173,7 +133,6 @@
|
||||
(defn reset-stack-history!
|
||||
[stack]
|
||||
(when stack
|
||||
(dbg :nav/reset-stack-history {:stack stack})
|
||||
(swap! stack-history assoc stack {:history [(stack-defaults stack)]})
|
||||
(swap! initialised-stacks dissoc stack)))
|
||||
|
||||
@@ -193,12 +152,7 @@
|
||||
(true? push) "push"
|
||||
:else "push"))]
|
||||
(reset! navigation-source nil)
|
||||
(dbg :nav/next-navigation
|
||||
:src src
|
||||
:intent intent
|
||||
:stack stack
|
||||
:first? first?
|
||||
:nav-type nav-type)
|
||||
|
||||
(when first?
|
||||
(swap! initialised-stacks assoc stack true))
|
||||
{:navigation-type nav-type
|
||||
@@ -207,7 +161,6 @@
|
||||
|
||||
(defn- notify-route-payload!
|
||||
[payload]
|
||||
(dbg :nav/notify-native payload)
|
||||
(-> (.routeDidChange mobile-util/ui-local (clj->js payload))
|
||||
(p/catch (fn [err]
|
||||
(log/warn :mobile-native-navigation/route-report-failed
|
||||
@@ -222,12 +175,6 @@
|
||||
:stack (or stack (current-stack))})
|
||||
stack (or stack (current-stack))
|
||||
path (or path (current-path))]
|
||||
(dbg :nav/notify-route-change
|
||||
:stack stack
|
||||
:navigation-type navigation-type
|
||||
:path path
|
||||
:route-to (or (:to route)
|
||||
(get-in route-match [:data :name])))
|
||||
(set-current-stack! stack)
|
||||
(remember-route! stack navigation-type route path route-match)
|
||||
(when (and (mobile-util/native-ios?)
|
||||
@@ -254,20 +201,16 @@
|
||||
"Activate a stack and restore its last known route."
|
||||
[stack]
|
||||
(when stack
|
||||
(let [stack (ensure-stack stack)]
|
||||
(dbg :nav/switch-stack {:to stack
|
||||
:current @active-stack
|
||||
:stack-top (select-keys (stack-top stack) [:path])})
|
||||
(let [stack (ensure-stack stack)
|
||||
current @active-stack]
|
||||
(set-current-stack! stack)
|
||||
(when-let [{:keys [path route route-match]} (stack-top stack)]
|
||||
(let [route-match (or route-match (:route-match (stack-defaults stack)))
|
||||
path (or path (current-path))]
|
||||
(dbg :nav/switch-stack-apply
|
||||
:stack stack
|
||||
:path path
|
||||
:route-name (or (get-in route [:data :name])
|
||||
(get-in route-match [:data :name])))
|
||||
(route-handler/set-route-match! route-match)
|
||||
(when (= current "search")
|
||||
;; reset to :home
|
||||
(orig-replace-state :home nil nil))
|
||||
(notify-route-change!
|
||||
{:route {:to (or (get-in route [:data :name])
|
||||
(get-in route-match [:data :name]))
|
||||
@@ -286,23 +229,14 @@
|
||||
(let [stack (current-stack)
|
||||
{:keys [history]} (get @stack-history stack)
|
||||
history (vec history)]
|
||||
(if (<= (count history) 1)
|
||||
(dbg :nav/pop-stack-root {:stack stack
|
||||
:history (mapv :path history)})
|
||||
(when (> (count history) 1)
|
||||
(let [new-history (subvec history 0 (dec (count history)))
|
||||
{:keys [route route-match path]} (peek new-history)
|
||||
{:keys [route-match]} (peek new-history)
|
||||
route-match (or route-match (:route-match (stack-defaults stack)))
|
||||
route-name (get-in route-match [:data :name])
|
||||
path-params (get-in route-match [:parameters :path])
|
||||
query-params (get-in route-match [:parameters :query])]
|
||||
|
||||
(dbg :nav/pop-stack
|
||||
:stack stack
|
||||
:old-history (mapv :path history)
|
||||
:new-history (mapv :path new-history)
|
||||
:target-path path
|
||||
:route-name route-name)
|
||||
|
||||
(swap! stack-history assoc stack {:history new-history})
|
||||
|
||||
;; Pretend this came from a pop for next-navigation!
|
||||
@@ -315,10 +249,6 @@
|
||||
|
||||
(defn ^:export install-native-bridge!
|
||||
[]
|
||||
(dbg :nav/install-native-bridge {})
|
||||
(set! (.-LogseqNative js/window)
|
||||
(clj->js
|
||||
{:onNativePop (fn []
|
||||
(dbg :nav/on-native-pop {:stack (current-stack)
|
||||
:path (current-path)})
|
||||
(pop-stack!))})))
|
||||
{:onNativePop (fn [] (pop-stack!))})))
|
||||
|
||||
Reference in New Issue
Block a user