Parcourir la source

enhance(ux): iOS search toolbar

Tienson Qin il y a 1 mois
Parent
commit
4b34769afd

+ 0 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -35,7 +35,6 @@
                 D3989CC32ECB0E5700D06615 /* LiquidTabsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CBF2ECB0E5700D06615 /* LiquidTabsStore.swift */; };
                 D3989CC32ECB0E5700D06615 /* LiquidTabsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CBF2ECB0E5700D06615 /* LiquidTabsStore.swift */; };
                 D3989CC42ECB0E5700D06615 /* LiquidTabsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CBD2ECB0E5700D06615 /* LiquidTabsPlugin.swift */; };
                 D3989CC42ECB0E5700D06615 /* LiquidTabsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CBD2ECB0E5700D06615 /* LiquidTabsPlugin.swift */; };
                 D3989CC52ECB0E5700D06615 /* NativeNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CC02ECB0E5700D06615 /* NativeNavHost.swift */; };
                 D3989CC52ECB0E5700D06615 /* NativeNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CC02ECB0E5700D06615 /* NativeNavHost.swift */; };
-                D3989CC62ECB0E5700D06615 /* SearchTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CC12ECB0E5700D06615 /* SearchTabView.swift */; };
                 D3989CC82ECB174A00D06615 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CC72ECB174A00D06615 /* SceneDelegate.swift */; };
                 D3989CC82ECB174A00D06615 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3989CC72ECB174A00D06615 /* SceneDelegate.swift */; };
                 D39D1FE02E7DAFB000C903D1 /* LogseqIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */; };
                 D39D1FE02E7DAFB000C903D1 /* LogseqIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */; };
                 D39D1FE12E7DAFB000C903D1 /* LogseqIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */; };
                 D39D1FE12E7DAFB000C903D1 /* LogseqIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */; };
@@ -116,7 +115,6 @@
                 D3989CBE2ECB0E5700D06615 /* LiquidTabsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiquidTabsRootView.swift; sourceTree = "<group>"; };
                 D3989CBE2ECB0E5700D06615 /* LiquidTabsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiquidTabsRootView.swift; sourceTree = "<group>"; };
                 D3989CBF2ECB0E5700D06615 /* LiquidTabsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiquidTabsStore.swift; sourceTree = "<group>"; };
                 D3989CBF2ECB0E5700D06615 /* LiquidTabsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiquidTabsStore.swift; sourceTree = "<group>"; };
                 D3989CC02ECB0E5700D06615 /* NativeNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeNavHost.swift; sourceTree = "<group>"; };
                 D3989CC02ECB0E5700D06615 /* NativeNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeNavHost.swift; sourceTree = "<group>"; };
-                D3989CC12ECB0E5700D06615 /* SearchTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTabView.swift; sourceTree = "<group>"; };
                 D3989CC72ECB174A00D06615 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
                 D3989CC72ECB174A00D06615 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
                 D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogseqIntents.swift; sourceTree = "<group>"; };
                 D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogseqIntents.swift; sourceTree = "<group>"; };
                 D3D62A09275C92880003FBDC /* FileContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileContainer.swift; sourceTree = "<group>"; };
                 D3D62A09275C92880003FBDC /* FileContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileContainer.swift; sourceTree = "<group>"; };
@@ -201,7 +199,6 @@
                                 D3989CBE2ECB0E5700D06615 /* LiquidTabsRootView.swift */,
                                 D3989CBE2ECB0E5700D06615 /* LiquidTabsRootView.swift */,
                                 D3989CBF2ECB0E5700D06615 /* LiquidTabsStore.swift */,
                                 D3989CBF2ECB0E5700D06615 /* LiquidTabsStore.swift */,
                                 D3989CC02ECB0E5700D06615 /* NativeNavHost.swift */,
                                 D3989CC02ECB0E5700D06615 /* NativeNavHost.swift */,
-                                D3989CC12ECB0E5700D06615 /* SearchTabView.swift */,
                                 D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */,
                                 D39D1FDF2E7DAFB000C903D1 /* LogseqIntents.swift */,
                                 A1B2C3D41E2F3A4B5C6D7E8F /* NativePageViewController.swift */,
                                 A1B2C3D41E2F3A4B5C6D7E8F /* NativePageViewController.swift */,
                                 A1B2C3D41E2F3A4B5C6D7E91 /* SharedWebViewController.swift */,
                                 A1B2C3D41E2F3A4B5C6D7E91 /* SharedWebViewController.swift */,
@@ -453,7 +450,6 @@
                                 D3989CC32ECB0E5700D06615 /* LiquidTabsStore.swift in Sources */,
                                 D3989CC32ECB0E5700D06615 /* LiquidTabsStore.swift in Sources */,
                                 D3989CC42ECB0E5700D06615 /* LiquidTabsPlugin.swift in Sources */,
                                 D3989CC42ECB0E5700D06615 /* LiquidTabsPlugin.swift in Sources */,
                                 D3989CC52ECB0E5700D06615 /* NativeNavHost.swift in Sources */,
                                 D3989CC52ECB0E5700D06615 /* NativeNavHost.swift in Sources */,
-                                D3989CC62ECB0E5700D06615 /* SearchTabView.swift in Sources */,
                                 D3989CC82ECB174A00D06615 /* SceneDelegate.swift in Sources */,
                                 D3989CC82ECB174A00D06615 /* SceneDelegate.swift in Sources */,
                                 5FF8632A283B5ADB0047731B /* Utils.swift in Sources */,
                                 5FF8632A283B5ADB0047731B /* Utils.swift in Sources */,
                                 CBF2D2E22DE95970006338BE /* AppViewController.swift in Sources */,
                                 CBF2D2E22DE95970006338BE /* AppViewController.swift in Sources */,

+ 109 - 26
ios/App/App/LiquidTabsRootView.swift

@@ -4,46 +4,129 @@ struct LiquidTabsRootView: View {
     @StateObject private var store = LiquidTabsStore.shared
     @StateObject private var store = LiquidTabsStore.shared
     let navController: UINavigationController
     let navController: UINavigationController
 
 
+    @State private var searchText: String = ""
+
+    // Convenience helpers: first two tabs from CLJS, rest ignored
+    private var firstTab: LiquidTab? {
+        store.tabs.first
+    }
+
+    private var secondTab: LiquidTab? {
+        store.tabs.count > 1 ? store.tabs[1] : nil
+    }
+
+    private var thirdTab: LiquidTab? {
+        store.tabs.count > 2 ? store.tabs[2] : nil
+    }
+
     var body: some View {
     var body: some View {
-        Group {
+        if #available(iOS 26.0, *) {
+            // iOS 26+: static TabView with a dedicated search tab (role: .search)
             if store.tabs.isEmpty {
             if store.tabs.isEmpty {
                 // Fallback: just show your existing nav + webview
                 // Fallback: just show your existing nav + webview
                 NativeNavHost(navController: navController)
                 NativeNavHost(navController: navController)
-                    .ignoresSafeArea()
+                  .ignoresSafeArea()
             } else {
             } else {
-                TabView(selection: Binding(
-                    get: { store.effectiveSelectedId() },
-                    set: { newValue in
-                        guard let id = newValue else { return }
-                        store.selectedId = id
-                        LiquidTabsPlugin.shared?.notifyTabSelected(id: id)
+                TabView {
+                    // ---- Tab 1 (normal) ----
+                    if let tab = firstTab {
+                        Tab {
+                            NativeNavHost(navController: navController)
+                              .ignoresSafeArea()
+                              .onAppear {
+                                  store.selectedId = tab.id
+                                  LiquidTabsPlugin.shared?.notifyTabSelected(id: tab.id)
+                              }
+                        } label: {
+                            Label(tab.title, systemImage: tab.systemImage)
+                        }
                     }
                     }
-                )) {
-                    ForEach(store.tabs) { tab in
-                        tabView(for: tab)
+
+                    // ---- Tab 2 (optional normal) ----
+                    if let tab = secondTab {
+                        Tab {
+                            NativeNavHost(navController: navController)
+                              .ignoresSafeArea()
+                              .onAppear {
+                                  store.selectedId = tab.id
+                                  LiquidTabsPlugin.shared?.notifyTabSelected(id: tab.id)
+                              }
+                        } label: {
+                            Label(tab.title, systemImage: tab.systemImage)
+                        }
+                    }
+
+                    if let tab = thirdTab {
+                        Tab {
+                            NativeNavHost(navController: navController)
+                              .ignoresSafeArea()
+                              .onAppear {
+                                  store.selectedId = tab.id
+                                  LiquidTabsPlugin.shared?.notifyTabSelected(id: tab.id)
+                              }
+                        } label: {
+                            Label(tab.title, systemImage: tab.systemImage)
+                        }
                     }
                     }
+
+                    // ---- Search tab (static, special pill) ----
+                    Tab(role: .search) {
+                        SearchView(searchText: $searchText)
+                          .onAppear {
+                              // Use a fixed id for search, or map it from CLJS if you prefer
+                              LiquidTabsPlugin.shared?.notifyTabSelected(id: "search")
+                          }
+                    }
+                }}
+
+        } else {
+            // iOS < 26: fall back to the old dynamic tabItem-based tabs
+            TabView(selection: Binding(
+                      get: { store.selectedId ?? firstTab?.id },
+                      set: { newValue in
+                guard let id = newValue else { return }
+                store.selectedId = id
+                LiquidTabsPlugin.shared?.notifyTabSelected(id: id)
+            }
+                    )) {
+                ForEach(store.tabs) { tab in
+                    NativeNavHost(navController: navController)
+                      .ignoresSafeArea()
+                      .tabItem {
+                          Label(tab.title, systemImage: tab.systemImage)
+                      }
+                      .tag(tab.id as String?)
                 }
                 }
             }
             }
         }
         }
     }
     }
+}
 
 
-    @ViewBuilder
-    private func tabView(for tab: LiquidTab) -> some View {
-        switch tab.role {
-        case .normal:
-            NativeNavHost(navController: navController)
-                .ignoresSafeArea()
-                .tabItem {
-                    Label(tab.title, systemImage: tab.systemImage)
-                }
-                .tag(tab.id as String?)
 
 
-        case .search:
-            SearchTabView()
-                .tabItem {
-                    Label(tab.title, systemImage: tab.systemImage)
+struct SearchView: View {
+    @Binding var searchText: String
+
+    var body: some View {
+        if #available(iOS 26.0, *) {
+            NavigationStack {
+                if #available(iOS 17.0, *) {
+                    ContentUnavailableView("Search", systemImage: "magnifyingglass")
+                      .navigationTitle("Search")
+                } else {
+                    Text("Search")
                 }
                 }
-                .tag(tab.id as String?)
+            }
+              .searchable(
+                text: $searchText,
+                placement: .automatic,
+                prompt: "Search"
+              )
+              .searchToolbarBehavior(.minimize)   // Liquid behavior on iOS 26
+              .onChange(of: searchText) { newValue in
+                  LiquidTabsPlugin.shared?.notifySearchChanged(query: newValue)
+              }
+        } else {
+            // Fallback on earlier versions
         }
         }
     }
     }
 }
 }

+ 0 - 30
ios/App/App/SearchTabView.swift

@@ -1,30 +0,0 @@
-import SwiftUI
-
-struct SearchTabView: View {
-    @State private var searchText: String = ""
-
-    var body: some View {
-        if #available(iOS 26.0, *) {
-            NavigationStack {
-                // Placeholder content – your web app will react to search anyway
-                if #available(iOS 17.0, *) {
-                    ContentUnavailableView("Search", systemImage: "magnifyingglass")
-                        .navigationTitle("Search")
-                } else {
-                    // Fallback on earlier versions
-                }
-            }
-            .searchable(
-                text: $searchText,
-                placement: .automatic,
-                prompt: "Search"
-            )
-            .searchToolbarBehavior(.minimize)
-            .onChange(of: searchText) { newValue in
-                LiquidTabsPlugin.shared?.notifySearchChanged(query: newValue)
-            }
-        } else {
-            // Fallback on earlier versions
-        }
-    }
-}

+ 6 - 4
src/main/mobile/bottom_tabs.cljs

@@ -61,12 +61,14 @@
   []
   []
   (p/do!
   (p/do!
    (configure-tabs
    (configure-tabs
-    [{:id "home"    :title "Home"    :systemImage "house"             :role "normal"}
-     {:id "quick-add" :title "Capture" :systemImage "plus"            :role "normal"}
-     {:id "settings" :title "Settings" :systemImage "gear"            :role "normal"}
-     {:id "search"  :title "Search"  :systemImage "magnifyingglass"   :role "search"}])
+    [{:id "home"      :title "Home"      :systemImage "house"             :role "normal"}
+     {:id "quick-add" :title "Capture" :systemImage "plus"                :role "normal"}
+     {:id "settings"  :title "Settings" :systemImage "gear"               :role "normal"}
+     ;; {:id "search"    :title "Search"    :systemImage "magnifyingglass"   :role "search"}
+     ])
    (add-tab-selected-listener!
    (add-tab-selected-listener!
     (fn [tab]
     (fn [tab]
+      (prn :debug :tab tab)
       (when-not (= tab "quick-add")
       (when-not (= tab "quick-add")
         (mobile-state/set-tab! tab))
         (mobile-state/set-tab! tab))
       (case tab
       (case tab