From 454363d195ba350f6c8dc5ecc0526109d8181976 Mon Sep 17 00:00:00 2001 From: Cats Juice Date: Wed, 20 Nov 2024 18:09:58 +0800 Subject: [PATCH] feat(mobile): disable swipe back gesture when there is no back in header --- .../ios/App/App.xcodeproj/project.pbxproj | 13 ++++- .../ios/App/App/AFFiNEViewController.swift | 1 + .../NavigationGesturePlugin.swift | 32 +++++++++++ packages/frontend/apps/ios/src/app.tsx | 7 +++ .../plugins/navigation-gesture/definitions.ts | 5 ++ .../src/plugins/navigation-gesture/index.ts | 9 ++++ .../components/mobile/config-modal/index.tsx | 2 +- .../core/src/components/mobile/index.ts | 1 - .../core/src/mobile/components/index.ts | 1 + .../components}/page-header/index.tsx | 14 +++++ .../components}/page-header/styles.css.ts | 0 .../dialogs/selectors/generic-selector.tsx | 2 +- .../frontend/core/src/mobile/modules/index.ts | 2 + .../modules/navigation-gesture/index.ts | 13 +++++ .../providers/navigation-gesture.ts | 10 ++++ .../services/navigation-gesture.ts | 54 +++++++++++++++++++ .../workspace/detail/mobile-detail-page.tsx | 21 ++++---- .../views/all-docs/collection/detail.tsx | 2 +- .../views/all-docs/tag/detail-header.tsx | 2 +- 19 files changed, 176 insertions(+), 15 deletions(-) create mode 100644 packages/frontend/apps/ios/App/App/plugins/NavigationGesture/NavigationGesturePlugin.swift create mode 100644 packages/frontend/apps/ios/src/plugins/navigation-gesture/definitions.ts create mode 100644 packages/frontend/apps/ios/src/plugins/navigation-gesture/index.ts rename packages/frontend/core/src/{components/mobile => mobile/components}/page-header/index.tsx (85%) rename packages/frontend/core/src/{components/mobile => mobile/components}/page-header/styles.css.ts (100%) create mode 100644 packages/frontend/core/src/mobile/modules/navigation-gesture/index.ts create mode 100644 packages/frontend/core/src/mobile/modules/navigation-gesture/providers/navigation-gesture.ts create mode 100644 packages/frontend/core/src/mobile/modules/navigation-gesture/services/navigation-gesture.ts diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj index c7453edb67599..a7d6f5f240407 100644 --- a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj +++ b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 9D90BE2D2CCB9876006677DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE222CCB9876006677DB /* Main.storyboard */; }; 9D90BE2E2CCB9876006677DB /* public in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE232CCB9876006677DB /* public */; }; C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; + E93B276C2CED92B1001409B8 /* NavigationGesturePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B276B2CED92B1001409B8 /* NavigationGesturePlugin.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -35,6 +36,7 @@ 9D90BE232CCB9876006677DB /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; + E93B276B2CED92B1001409B8 /* NavigationGesturePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationGesturePlugin.swift; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -65,7 +67,6 @@ 504EC3051FED79650016851F /* Products */, 7F8756D8B27F46E3366F6CEA /* Pods */, 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */, - 9D6A85312CCF6D6B00DAB35F /* Recovered References */, ); indentWidth = 2; sourceTree = ""; @@ -101,6 +102,7 @@ 9D90BE1A2CCB9876006677DB /* plugins */ = { isa = PBXGroup; children = ( + E93B276A2CED9298001409B8 /* NavigationGesture */, 9D90BE192CCB9876006677DB /* Cookie */, ); path = plugins; @@ -122,6 +124,14 @@ path = App; sourceTree = ""; }; + E93B276A2CED9298001409B8 /* NavigationGesture */ = { + isa = PBXGroup; + children = ( + E93B276B2CED92B1001409B8 /* NavigationGesturePlugin.swift */, + ); + path = NavigationGesture; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -234,6 +244,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E93B276C2CED92B1001409B8 /* NavigationGesturePlugin.swift in Sources */, 9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */, 9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */, 9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */, diff --git a/packages/frontend/apps/ios/App/App/AFFiNEViewController.swift b/packages/frontend/apps/ios/App/App/AFFiNEViewController.swift index bddedfec4b8a4..e993eeeb613c9 100644 --- a/packages/frontend/apps/ios/App/App/AFFiNEViewController.swift +++ b/packages/frontend/apps/ios/App/App/AFFiNEViewController.swift @@ -11,5 +11,6 @@ class AFFiNEViewController: CAPBridgeViewController { override func capacitorDidLoad() { bridge?.registerPluginInstance(CookiePlugin()) bridge?.registerPluginInstance(HashcashPlugin()) + bridge?.registerPluginInstance(NavigationGesturePlugin()) } } diff --git a/packages/frontend/apps/ios/App/App/plugins/NavigationGesture/NavigationGesturePlugin.swift b/packages/frontend/apps/ios/App/App/plugins/NavigationGesture/NavigationGesturePlugin.swift new file mode 100644 index 0000000000000..045fce128862e --- /dev/null +++ b/packages/frontend/apps/ios/App/App/plugins/NavigationGesture/NavigationGesturePlugin.swift @@ -0,0 +1,32 @@ +import Foundation +import Capacitor + +@objc(NavigationGesturePlugin) +public class NavigationGesturePlugin: CAPPlugin, CAPBridgedPlugin { + public let identifier = "NavigationGesturePlugin" + public let jsName = "NavigationGesture" + public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "isEnabled", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "enable", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "disable", returnType: CAPPluginReturnPromise) + ] + + @objc func isEnabled(_ call: CAPPluginCall) { + let enabled = self.bridge?.webView?.allowsBackForwardNavigationGestures ?? true + call.resolve(["value": enabled]) + } + + @objc func enable(_ call: CAPPluginCall) { + DispatchQueue.main.sync { + self.bridge?.webView?.allowsBackForwardNavigationGestures = true + call.resolve([:]) + } + } + + @objc func disable(_ call: CAPPluginCall) { + DispatchQueue.main.sync { + self.bridge?.webView?.allowsBackForwardNavigationGestures = false + call.resolve([:]) + } + } +} diff --git a/packages/frontend/apps/ios/src/app.tsx b/packages/frontend/apps/ios/src/app.tsx index 327871da60aa4..31e1d781f1ffe 100644 --- a/packages/frontend/apps/ios/src/app.tsx +++ b/packages/frontend/apps/ios/src/app.tsx @@ -1,6 +1,7 @@ import { AffineContext } from '@affine/core/components/context'; import { AppContainer } from '@affine/core/desktop/components/app-container'; import { configureMobileModules } from '@affine/core/mobile/modules'; +import { NavigationGestureProvider } from '@affine/core/mobile/modules/navigation-gesture'; import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard'; import { router } from '@affine/core/mobile/router'; import { configureCommonModules } from '@affine/core/modules'; @@ -34,6 +35,7 @@ import { RouterProvider } from 'react-router-dom'; import { configureFetchProvider } from './fetch'; import { Cookie } from './plugins/cookie'; import { Hashcash } from './plugins/hashcash'; +import { NavigationGesture } from './plugins/navigation-gesture'; const future = { v7_startTransition: true, @@ -86,6 +88,11 @@ framework.impl(VirtualKeyboardProvider, { Keyboard.removeAllListeners(); }, }); +framework.impl(NavigationGestureProvider, { + isEnabled: () => NavigationGesture.isEnabled(), + enable: () => NavigationGesture.enable(), + disable: () => NavigationGesture.disable(), +}); const frameworkProvider = framework.provider(); // setup application lifecycle events, and emit application start event diff --git a/packages/frontend/apps/ios/src/plugins/navigation-gesture/definitions.ts b/packages/frontend/apps/ios/src/plugins/navigation-gesture/definitions.ts new file mode 100644 index 0000000000000..c65018514ea6d --- /dev/null +++ b/packages/frontend/apps/ios/src/plugins/navigation-gesture/definitions.ts @@ -0,0 +1,5 @@ +export interface NavigationGesturePlugin { + isEnabled: () => Promise; + enable: () => Promise; + disable: () => Promise; +} diff --git a/packages/frontend/apps/ios/src/plugins/navigation-gesture/index.ts b/packages/frontend/apps/ios/src/plugins/navigation-gesture/index.ts new file mode 100644 index 0000000000000..9215c1373219d --- /dev/null +++ b/packages/frontend/apps/ios/src/plugins/navigation-gesture/index.ts @@ -0,0 +1,9 @@ +import { registerPlugin } from '@capacitor/core'; + +import type { NavigationGesturePlugin } from './definitions'; + +const NavigationGesture = + registerPlugin('NavigationGesture'); + +export * from './definitions'; +export { NavigationGesture }; diff --git a/packages/frontend/core/src/components/mobile/config-modal/index.tsx b/packages/frontend/core/src/components/mobile/config-modal/index.tsx index e91edb8db4dc8..2e0c07a173ce1 100644 --- a/packages/frontend/core/src/components/mobile/config-modal/index.tsx +++ b/packages/frontend/core/src/components/mobile/config-modal/index.tsx @@ -1,4 +1,5 @@ import { Button, Modal } from '@affine/component'; +import { PageHeader } from '@affine/core/mobile/components'; import { useI18n } from '@affine/i18n'; import clsx from 'clsx'; import { @@ -8,7 +9,6 @@ import { type ReactNode, } from 'react'; -import { PageHeader } from '../page-header'; import * as styles from './styles.css'; interface ConfigModalProps { diff --git a/packages/frontend/core/src/components/mobile/index.ts b/packages/frontend/core/src/components/mobile/index.ts index d9dfbc4fa24e7..3bbf215aeae83 100644 --- a/packages/frontend/core/src/components/mobile/index.ts +++ b/packages/frontend/core/src/components/mobile/index.ts @@ -1,2 +1 @@ export * from './config-modal'; -export * from './page-header'; diff --git a/packages/frontend/core/src/mobile/components/index.ts b/packages/frontend/core/src/mobile/components/index.ts index 2cbbc0f58069d..18dd7f3f65612 100644 --- a/packages/frontend/core/src/mobile/components/index.ts +++ b/packages/frontend/core/src/mobile/components/index.ts @@ -1,5 +1,6 @@ export * from './app-tabs'; export * from './doc-card'; +export * from './page-header'; export * from './rename'; export * from './search-input'; export * from './search-result'; diff --git a/packages/frontend/core/src/components/mobile/page-header/index.tsx b/packages/frontend/core/src/mobile/components/page-header/index.tsx similarity index 85% rename from packages/frontend/core/src/components/mobile/page-header/index.tsx rename to packages/frontend/core/src/mobile/components/page-header/index.tsx index bcc22e03de01e..0fcff66700dd7 100644 --- a/packages/frontend/core/src/components/mobile/page-header/index.tsx +++ b/packages/frontend/core/src/mobile/components/page-header/index.tsx @@ -1,13 +1,16 @@ import { IconButton, SafeArea } from '@affine/component'; import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc'; +import { useService } from '@toeverything/infra'; import clsx from 'clsx'; import { forwardRef, type HtmlHTMLAttributes, type ReactNode, useCallback, + useEffect, } from 'react'; +import { NavigationGestureService } from '../../modules/navigation-gesture'; import * as styles from './styles.css'; export interface PageHeaderProps @@ -60,6 +63,17 @@ export const PageHeader = forwardRef( }, ref ) { + const navigationGesture = useService(NavigationGestureService); + + useEffect(() => { + const prev = navigationGesture.enabled$.value; + navigationGesture.setEnabled(!!back); + + return () => { + navigationGesture.setEnabled(prev); + }; + }, [back, navigationGesture]); + const handleRouteBack = useCallback(() => { backAction ? backAction() : history.back(); }, [backAction]); diff --git a/packages/frontend/core/src/components/mobile/page-header/styles.css.ts b/packages/frontend/core/src/mobile/components/page-header/styles.css.ts similarity index 100% rename from packages/frontend/core/src/components/mobile/page-header/styles.css.ts rename to packages/frontend/core/src/mobile/components/page-header/styles.css.ts diff --git a/packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx index 24eda1497d863..99815b8dab821 100644 --- a/packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx +++ b/packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx @@ -5,7 +5,7 @@ import { Scrollable, useThemeColorMeta, } from '@affine/component'; -import { PageHeader } from '@affine/core/components/mobile'; +import { PageHeader } from '@affine/core/mobile/components'; import { useI18n } from '@affine/i18n'; import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; import { cssVarV2 } from '@toeverything/theme/v2'; diff --git a/packages/frontend/core/src/mobile/modules/index.ts b/packages/frontend/core/src/mobile/modules/index.ts index 106fdfc1c4e73..c316ca05fbca6 100644 --- a/packages/frontend/core/src/mobile/modules/index.ts +++ b/packages/frontend/core/src/mobile/modules/index.ts @@ -1,9 +1,11 @@ import type { Framework } from '@toeverything/infra'; +import { configureMobileNavigationGestureModule } from './navigation-gesture'; import { configureMobileSearchModule } from './search'; import { configureMobileVirtualKeyboardModule } from './virtual-keyboard'; export function configureMobileModules(framework: Framework) { configureMobileSearchModule(framework); configureMobileVirtualKeyboardModule(framework); + configureMobileNavigationGestureModule(framework); } diff --git a/packages/frontend/core/src/mobile/modules/navigation-gesture/index.ts b/packages/frontend/core/src/mobile/modules/navigation-gesture/index.ts new file mode 100644 index 0000000000000..efe25a8e3e0f4 --- /dev/null +++ b/packages/frontend/core/src/mobile/modules/navigation-gesture/index.ts @@ -0,0 +1,13 @@ +import type { Framework } from '@toeverything/infra'; + +import { NavigationGestureProvider } from './providers/navigation-gesture'; +import { NavigationGestureService } from './services/navigation-gesture'; + +export { NavigationGestureProvider, NavigationGestureService }; + +export function configureMobileNavigationGestureModule(framework: Framework) { + framework.service( + NavigationGestureService, + f => new NavigationGestureService(f.getOptional(NavigationGestureProvider)) + ); +} diff --git a/packages/frontend/core/src/mobile/modules/navigation-gesture/providers/navigation-gesture.ts b/packages/frontend/core/src/mobile/modules/navigation-gesture/providers/navigation-gesture.ts new file mode 100644 index 0000000000000..4906c19877e29 --- /dev/null +++ b/packages/frontend/core/src/mobile/modules/navigation-gesture/providers/navigation-gesture.ts @@ -0,0 +1,10 @@ +import { createIdentifier } from '@toeverything/infra'; + +export interface NavigationGestureProvider { + isEnabled: () => Promise; + enable: () => Promise; + disable: () => Promise; +} + +export const NavigationGestureProvider = + createIdentifier('NavigationGestureProvider'); diff --git a/packages/frontend/core/src/mobile/modules/navigation-gesture/services/navigation-gesture.ts b/packages/frontend/core/src/mobile/modules/navigation-gesture/services/navigation-gesture.ts new file mode 100644 index 0000000000000..5f0d28d4ae33a --- /dev/null +++ b/packages/frontend/core/src/mobile/modules/navigation-gesture/services/navigation-gesture.ts @@ -0,0 +1,54 @@ +import { + effect, + exhaustMapWithTrailing, + fromPromise, + LiveData, + Service, +} from '@toeverything/infra'; +import { catchError, distinctUntilChanged, EMPTY, mergeMap } from 'rxjs'; + +import type { NavigationGestureProvider } from '../providers/navigation-gesture'; + +export class NavigationGestureService extends Service { + public enabled$ = new LiveData(false); + + constructor( + private readonly navigationGestureProvider?: NavigationGestureProvider + ) { + super(); + this.disable().catch(console.error); + } + + setEnabled = effect( + distinctUntilChanged(), + exhaustMapWithTrailing((enable: boolean) => { + return fromPromise(async () => { + if (!this.navigationGestureProvider) { + return; + } + if (enable) { + await this.navigationGestureProvider.enable(); + } else { + await this.navigationGestureProvider.disable(); + } + return; + }).pipe( + mergeMap(() => EMPTY), + catchError(err => { + console.error('navigationGestureProvider error', err); + return EMPTY; + }) + ); + }) + ); + + async enable() { + this.enabled$.next(true); + return this.navigationGestureProvider?.enable(); + } + + async disable() { + this.enabled$.next(false); + return this.navigationGestureProvider?.disable(); + } +} diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx index 01ce08d1a6f2c..10710e663690a 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx @@ -7,9 +7,9 @@ import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite- import { usePageDocumentTitle } from '@affine/core/components/hooks/use-global-state'; import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; -import { PageHeader } from '@affine/core/components/mobile'; import { PageDetailEditor } from '@affine/core/components/page-detail-editor'; import { DetailPageWrapper } from '@affine/core/desktop/pages/workspace/detail-page/detail-page-wrapper'; +import { PageHeader } from '@affine/core/mobile/components'; import { EditorService } from '@affine/core/modules/editor'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchService } from '@affine/core/modules/workbench'; @@ -202,19 +202,22 @@ const DetailPageImpl = () => { ); }; -const skeleton = ( +const getSkeleton = (back: boolean) => ( <> - + ); - -const notFound = ( +const getNotFound = (back: boolean) => ( <> - + Page Not Found (TODO) ); +const skeleton = getSkeleton(false); +const skeletonWithBack = getSkeleton(true); +const notFound = getNotFound(false); +const notFoundWithBack = getNotFound(true); const MobileDetailPage = ({ pageId, @@ -237,12 +240,12 @@ const MobileDetailPage = ({ return (
diff --git a/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx b/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx index 95c157f8201d7..e1a688f567d17 100644 --- a/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx +++ b/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx @@ -1,7 +1,7 @@ import { IconButton, MobileMenu } from '@affine/component'; import { EmptyCollectionDetail } from '@affine/core/components/affine/empty'; -import { PageHeader } from '@affine/core/components/mobile'; import { isEmptyCollection } from '@affine/core/desktop/pages/workspace/collection'; +import { PageHeader } from '@affine/core/mobile/components'; import type { Collection } from '@affine/env/filter'; import { MoreHorizontalIcon, ViewLayersIcon } from '@blocksuite/icons/rc'; diff --git a/packages/frontend/core/src/mobile/views/all-docs/tag/detail-header.tsx b/packages/frontend/core/src/mobile/views/all-docs/tag/detail-header.tsx index be56ae9208252..c588258089030 100644 --- a/packages/frontend/core/src/mobile/views/all-docs/tag/detail-header.tsx +++ b/packages/frontend/core/src/mobile/views/all-docs/tag/detail-header.tsx @@ -1,5 +1,5 @@ import { IconButton, MobileMenu } from '@affine/component'; -import { PageHeader } from '@affine/core/components/mobile'; +import { PageHeader } from '@affine/core/mobile/components'; import type { Tag } from '@affine/core/modules/tag'; import { MoreHorizontalIcon } from '@blocksuite/icons/rc'; import { useLiveData } from '@toeverything/infra';