Просмотр исходного кода

fix(mobile): blink when capture

Tienson Qin 2 месяцев назад
Родитель
Сommit
3366155fad

+ 16 - 2
ios/App/App/LiquidTabsPlugin.swift

@@ -44,14 +44,23 @@ public class LiquidTabsPlugin: CAPPlugin, CAPBridgedPlugin {
 
             let systemImage = dict["systemImage"] as? String ?? "square"
             let roleStr = dict["role"] as? String ?? "normal"
-            let role: LiquidTab.Role = (roleStr == "search") ? .search : .normal
+            let role: LiquidTab.Role
+
+            switch roleStr {
+            case "search":
+                role = .search
+            case "action":
+                role = .action
+            default:
+                role = .normal
+            }
 
             return LiquidTab(id: id, title: title, systemImage: systemImage, role: role)
         }
 
         DispatchQueue.main.async {
             self.store.tabs = tabs
-            if let firstId = tabs.first?.id {
+            if let firstId = tabs.first(where: { !$0.isActionButton })?.id ?? tabs.first?.id {
                 self.store.selectedId = firstId
             }
         }
@@ -68,6 +77,11 @@ public class LiquidTabsPlugin: CAPPlugin, CAPBridgedPlugin {
         }
 
         DispatchQueue.main.async {
+            if let tab = self.store.tab(for: id), tab.isActionButton {
+                LiquidTabsPlugin.shared?.notifyTabSelected(id: id)
+                return
+            }
+
             self.store.selectedId = id
         }
 

+ 60 - 21
ios/App/App/LiquidTabsRootView.swift

@@ -121,6 +121,19 @@ private struct LiquidTabs26View: View {
 
     private let maxMainTabs = 6
 
+    private func tab(for selection: LiquidTabsTabSelection) -> LiquidTab? {
+        guard let id = store.tabId(for: selection) else { return nil }
+        return store.tab(for: id)
+    }
+
+    @discardableResult
+    private func handleActionIfNeeded(selection: LiquidTabsTabSelection) -> Bool {
+        guard let tab = tab(for: selection), tab.isActionButton else { return false }
+
+        LiquidTabsPlugin.shared?.notifyTabSelected(id: tab.id)
+        return true
+    }
+
     // Proxy binding to intercept re-taps
     private var tabSelectionProxy: Binding<LiquidTabsTabSelection> {
         Binding(
@@ -128,7 +141,7 @@ private struct LiquidTabs26View: View {
             set: { newValue in
                 if newValue == selectedTab {
                     handleRetap(on: newValue)
-                } else {
+                } else if !handleActionIfNeeded(selection: newValue) {
                     selectedTab = newValue
                 }
             }
@@ -146,24 +159,27 @@ private struct LiquidTabs26View: View {
 
     private func initialSelection() -> LiquidTabsTabSelection {
         if let id = store.selectedId,
+           let tab = store.tab(for: id),
+           !tab.isActionButton,
            let sel = store.selection(forId: id) {
             return sel
         }
 
-        if !store.tabs.isEmpty {
-            return .content(0)
+        if let firstIndex = store.tabs.prefix(maxMainTabs).firstIndex(where: { !$0.isActionButton }) {
+            return .content(firstIndex)
         }
 
         return .search
     }
 
     private func focusSearchField() {
-        // Drive focus (and keyboard) only through searchFocused.
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
             isSearchFocused = true
         }
     }
 
+    // MARK: - Body
+
     var body: some View {
         if store.tabs.isEmpty {
             // bootstrap webview so JS can configure tabs
@@ -175,29 +191,46 @@ private struct LiquidTabs26View: View {
                 Color.logseqBackground.ignoresSafeArea()
 
                 TabView(selection: tabSelectionProxy) {
-                    // Dynamic main tabs using Tab(...) API
+                    // Dynamic main tabs – all use Tab(...)
                     ForEach(Array(store.tabs.prefix(maxMainTabs).enumerated()),
                             id: \.element.id) { index, tab in
                         Tab(
-                            tab.title,
-                            systemImage: tab.systemImage,
-                            value: LiquidTabsTabSelection.content(index)
+                          tab.title,
+                          systemImage: tab.systemImage,
+                          value: LiquidTabsTabSelection.content(index)
                         ) {
-                            NativeNavHost(navController: navController)
-                                .ignoresSafeArea()
-                                .background(Color.logseqBackground)
+                            if tab.isActionButton {
+                                // Capture / action tab: no real content, acts like a plain button
+                                NavigationStack {
+                                    VStack(spacing: 20) {
+                                        Text("Capture")
+                                          .font(.largeTitle)
+                                          .fontWeight(.bold)
+                                          .padding(.top, 120)
+
+                                        Spacer()
+                                    }
+                                    .frame(maxWidth: .infinity, maxHeight: .infinity)
+                                    .background(Color.logseqBackground)
+                                    .ignoresSafeArea()
+                                }
+                            } else {
+                                NativeNavHost(navController: navController)
+                                  .ignoresSafeArea()
+                                  .background(Color.logseqBackground)
+                            }
                         }
                     }
 
                     // Search Tab
                     Tab(value: .search, role: .search) {
                         SearchTabHost26(
-                            navController: navController,
-                            selectedTab: $selectedTab,
-                            firstTabId: store.tabs.first?.id,
-                            store: store
+                          navController: navController,
+                          selectedTab: $selectedTab,
+                          firstTabId: store.tabs.first?.id,
+                          store: store
                         )
-                        .ignoresSafeArea()
+                          .ignoresSafeArea()
                     }
                 }
                 .searchable(
@@ -249,8 +282,6 @@ private struct LiquidTabs26View: View {
 
                 switch newValue {
                 case .search:
-                    // Every time we switch to the search tab, re-focus the search
-                    // field so the search bar auto-focuses and keyboard appears.
                     DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                         hackShowKeyboard = true
                     }
@@ -262,13 +293,14 @@ private struct LiquidTabs26View: View {
                     focusSearchField()
 
                 case .content:
-                    // Leaving search tab – drop focus and stop hack keyboard.
                     isSearchFocused = false
                     hackShowKeyboard = false
                 }
             }
             .onChange(of: store.selectedId) { newId in
                 guard let id = newId,
+                      let tab = store.tab(for: id),
+                      !tab.isActionButton,
                       let newSelection = store.selection(forId: id) else {
                     return
                 }
@@ -282,6 +314,7 @@ private struct LiquidTabs26View: View {
     }
 }
 
+
 // Search host for 26+
 // Only responsible for cancel behaviour and tab switching.
 // It does NOT own the focus anymore.
@@ -342,11 +375,16 @@ private struct LiquidTabs16View: View {
 
                     TabView(selection: Binding<String?>(
                         get: {
-                            store.selectedId ?? store.firstTab?.id
+                            store.selectedId ?? store.tabs.first(where: { !$0.isActionButton })?.id ?? store.firstTab?.id
                         },
                         set: { newValue in
                             guard let id = newValue else { return }
 
+                            if let tab = store.tab(for: id), tab.isActionButton {
+                                LiquidTabsPlugin.shared?.notifyTabSelected(id: id)
+                                return
+                            }
+
                             // Re-tap: pop to root
                             if id == store.selectedId {
                                 navController.popToRootViewController(animated: true)
@@ -386,7 +424,8 @@ private struct LiquidTabs16View: View {
                 }
                 .onAppear {
                     if store.selectedId == nil {
-                        store.selectedId = store.tabs.first?.id
+                        store.selectedId = store.tabs.first(where: { !$0.isActionButton })?.id
+                            ?? store.tabs.first?.id
                     }
 
                     let appearance = UITabBarAppearance()

+ 14 - 0
ios/App/App/LiquidTabsStore.swift

@@ -10,6 +10,20 @@ struct LiquidTab: Identifiable, Equatable {
     enum Role {
         case normal
         case search
+        case action
+    }
+}
+
+extension LiquidTab {
+    /// Tabs that should behave like plain buttons instead of driving selection.
+    /// Defaults to the existing capture tab unless explicitly marked as an action.
+    var isActionButton: Bool {
+        switch role {
+        case .action:
+            return true
+        default:
+            return id == "capture"
+        }
     }
 }
 

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

@@ -120,5 +120,5 @@
   (configure-tabs
    [{:id "home"       :title "Home"       :systemImage "house" :role "normal"}
     {:id "favorites"  :title "Favorites"  :systemImage "star"  :role "normal"}
-    {:id "capture"    :title "Capture"    :systemImage "tray"  :role "normal"}
+    {:id "capture"    :title "Capture"    :systemImage "tray"  :role "action"}
     {:id "settings"   :title "Settings"   :systemImage "gear"  :role "normal"}]))

+ 10 - 4
src/main/mobile/components/popup.cljs

@@ -7,6 +7,7 @@
             [logseq.shui.popup.core :as shui-popup]
             [logseq.shui.ui :as shui]
             [mobile.state :as mobile-state]
+            [promesa.core :as p]
             [rum.core :as rum]))
 
 (defonce *last-popup? (atom nil))
@@ -51,10 +52,15 @@
         (editor-handler/quick-add-open-last-block!))
 
       dismissing?
-      (when (some? @mobile-state/*popup-data)
-        (state/pub-event! [:mobile/clear-edit])
-        (mobile-state/set-popup! nil)
-        (reset! *last-popup-data nil))
+      (do
+        (when (mobile-state/quick-add-open?)
+          (when-let [tab @mobile-state/*tab]
+            (mobile-state/set-tab! tab)))
+        (when (some? @mobile-state/*popup-data)
+          (p/do!
+           (mobile-state/set-popup! nil)
+           (reset! *last-popup-data nil)
+           (state/pub-event! [:mobile/clear-edit]))))
 
       :else
       nil)))