mirror of
https://github.com/logseq/logseq.git
synced 2026-05-25 21:24:21 +00:00
enhance: mobile multi navigation stacks
This commit is contained in:
@@ -98,8 +98,9 @@
|
||||
(js/console.log "Native search query" q)
|
||||
(reset! mobile-state/*search-input q)
|
||||
(reset! mobile-state/*search-last-input-at (common-util/time-ms))
|
||||
(when (= :page (state/get-current-route))
|
||||
(mobile-nav/reset-route!))))
|
||||
(comment
|
||||
(when (= :page (state/get-current-route))
|
||||
(mobile-nav/reset-route!)))))
|
||||
(add-keyboard-hack-listener!)))
|
||||
|
||||
(defn configure
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
;; so it is available even in :advanced release builds
|
||||
(prn "[Mobile] init!")
|
||||
(log/add-handler mobile-state/log-append!)
|
||||
(mobile-nav/install-native-bridge!)
|
||||
(set-router!)
|
||||
(init/init!)
|
||||
(fhandler/start! render!))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
[promesa.core :as p]
|
||||
[reitit.frontend.easy :as rfe]))
|
||||
|
||||
;; Each tab owns a navigation stack
|
||||
(defonce navigation-source (atom nil))
|
||||
(defonce ^:private initialised-stacks (atom {}))
|
||||
(def ^:private primary-stack "home")
|
||||
@@ -15,8 +16,30 @@
|
||||
(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)))
|
||||
(.addEventListener js/window "popstate" (fn [_]
|
||||
(reset! navigation-source :pop)
|
||||
(dbg :nav/popstate {:source :popstate})))
|
||||
|
||||
(defn current-stack
|
||||
[]
|
||||
@@ -25,6 +48,7 @@
|
||||
(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]
|
||||
@@ -38,10 +62,6 @@
|
||||
(let [p (strip-fragment (.-hash js/location))]
|
||||
(if (string/blank? p) "/" p)))
|
||||
|
||||
(defn- virtual-path?
|
||||
[path]
|
||||
(and (string? path) (string/starts-with? path "/__stack__/")))
|
||||
|
||||
(defn- stack-defaults
|
||||
[stack]
|
||||
(let [name (keyword stack)
|
||||
@@ -57,6 +77,7 @@
|
||||
(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})))
|
||||
|
||||
@@ -70,6 +91,7 @@
|
||||
([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)
|
||||
@@ -81,6 +103,7 @@
|
||||
([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!
|
||||
@@ -88,6 +111,7 @@
|
||||
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)))
|
||||
|
||||
@@ -108,6 +132,14 @@
|
||||
[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
|
||||
@@ -128,6 +160,12 @@
|
||||
(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)))))
|
||||
@@ -135,6 +173,7 @@
|
||||
(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)))
|
||||
|
||||
@@ -154,6 +193,12 @@
|
||||
(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
|
||||
@@ -162,6 +207,7 @@
|
||||
|
||||
(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
|
||||
@@ -169,17 +215,19 @@
|
||||
:payload payload})))))
|
||||
|
||||
(defn notify-route-change!
|
||||
"Inform native iOS layer of a route change to keep native stack in sync.
|
||||
{route {to keyword, path-params map, query-params map}
|
||||
route-match map ;; optional full route match for fast restoration
|
||||
path string ;; optional, e.g. \"/page/Today\"
|
||||
push boolean? ;; optional, explicit push vs replace hint}"
|
||||
"Inform native iOS layer of a route change to keep native stack in sync."
|
||||
[{:keys [route route-match path push stack]}]
|
||||
(let [{:keys [navigation-type push? stack]} (next-navigation! {:push push
|
||||
:nav-type (:navigation-type route-match)
|
||||
: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?)
|
||||
@@ -191,27 +239,86 @@
|
||||
path (assoc :path (strip-fragment path)))]
|
||||
(notify-route-payload! payload)))))
|
||||
|
||||
(defn reset-route!
|
||||
[]
|
||||
(route-handler/redirect-to-home! false)
|
||||
(let [stack (current-stack)]
|
||||
(reset-stack-history! stack)
|
||||
(notify-route-payload!
|
||||
{:navigationType "reset"
|
||||
:push false
|
||||
:stack stack})))
|
||||
(comment
|
||||
(defn reset-route!
|
||||
[]
|
||||
(route-handler/redirect-to-home! false)
|
||||
(let [stack (current-stack)]
|
||||
(reset-stack-history! stack)
|
||||
(notify-route-payload!
|
||||
{:navigationType "reset"
|
||||
:push false
|
||||
:stack stack}))))
|
||||
|
||||
(defn switch-stack!
|
||||
"Activate a stack and restore its last known route if different from current location."
|
||||
"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])})
|
||||
(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))]
|
||||
;; Update local route state immediately for UI (header, page context) without full router churn.
|
||||
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)
|
||||
;; Avoid triggering native navigation on stack switches; we rely on per-stack
|
||||
;; history and UI updates handled in JS for snappy tab changes.
|
||||
)))))
|
||||
(notify-route-change!
|
||||
{:route {:to (or (get-in route [:data :name])
|
||||
(get-in route-match [:data :name]))
|
||||
:path-params (or (:path-params route)
|
||||
(get-in route-match [:parameters :path]))
|
||||
:query-params (or (:query-params route)
|
||||
(get-in route-match [:parameters :query]))}
|
||||
:path path
|
||||
:stack stack
|
||||
:push false}))))))
|
||||
|
||||
(defn pop-stack!
|
||||
"Pop one route from the current stack, update router via replace-state.
|
||||
Called when native UINavigationController pops (back gesture / back button)."
|
||||
[]
|
||||
(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)})
|
||||
(let [new-history (subvec history 0 (dec (count history)))
|
||||
{:keys [route route-match path]} (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!
|
||||
(reset! navigation-source :pop)
|
||||
|
||||
;; Use *original* replace-state to avoid recording a :replace intent.
|
||||
(orig-replace-state route-name path-params query-params)
|
||||
|
||||
(route-handler/set-route-match! route-match)))))
|
||||
|
||||
(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!))})))
|
||||
|
||||
Reference in New Issue
Block a user