Browse Source

fix: slow rendering after searching and back to home

Tienson Qin 3 days ago
parent
commit
f1f8f22de5

+ 65 - 65
ios/App/App/AppDelegate.swift

@@ -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
         }
     }
+
 }
 
 // ---------------------------------------------------------

+ 0 - 1
ios/App/App/LiquidTabsRootView.swift

@@ -320,7 +320,6 @@ private struct SearchTabHost26: View {
                         wasSearching = false
                         selectedTab.wrappedValue = .content(0)
                         store.selectedId = firstId
-                        LiquidTabsPlugin.shared?.notifyTabSelected(id: firstId)
                     }
                 }
         }

+ 0 - 1
src/main/mobile/bottom_tabs.cljs

@@ -6,7 +6,6 @@
             [frontend.state :as state]
             [frontend.util :as util]
             [logseq.common.util :as common-util]
-            [mobile.navigation :as mobile-nav]
             [mobile.state :as mobile-state]))
 
 ;; Capacitor plugin instance:

+ 1 - 1
src/main/mobile/components/app.cljs

@@ -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

+ 11 - 81
src/main/mobile/navigation.cljs

@@ -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,20 +126,13 @@
                          (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}]
+        (swap! stack-history update stack (fn [{:keys [history]}]
                                             {:history (update-history history)}))
         (swap! initialised-stacks assoc stack true)))))
 
 (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!))})))