-
Notifications
You must be signed in to change notification settings - Fork 1.5k
refactor: migrate ShareExtensionStack to React Navigation static config #7413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { useNavigation } from '@react-navigation/native'; | ||
| import { type ComponentType } from 'react'; | ||
|
|
||
| // Screen-registration-only HOC: injects navigation via hook; does not forward refs (React Navigation static API renders screens without refs). | ||
| function withNavigation<P extends { navigation: any }>(WrappedComponent: ComponentType<P>): ComponentType<Omit<P, 'navigation'>> { | ||
| const WithNavigation = (props: Omit<P, 'navigation'>) => { | ||
| const navigation = useNavigation(); | ||
| return <WrappedComponent {...(props as P)} navigation={navigation} />; | ||
| }; | ||
| WithNavigation.displayName = `WithNavigation(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; | ||
| return WithNavigation; | ||
| } | ||
|
|
||
| export default withNavigation; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,39 @@ | ||
| import { useContext } from 'react'; | ||
| import { useContext, type ComponentType } from 'react'; | ||
| import { createNativeStackNavigator } from '@react-navigation/native-stack'; | ||
| import { type StaticParamList, type StaticScreenProps } from '@react-navigation/native'; | ||
|
|
||
| import { ThemeContext } from '../theme'; | ||
| import { defaultHeader, themedHeader } from '../lib/methods/helpers/navigation'; | ||
| import SelectServerView from '../views/SelectServerView'; | ||
| import ShareListView from '../views/ShareListView'; | ||
| import ShareView from '../views/ShareView'; | ||
| import withNavigation from '../lib/navigation/withNavigation'; | ||
| import { type InsideStackParamList } from './types'; | ||
|
|
||
| const ShareExtension = createNativeStackNavigator<any>(); | ||
| const ShareExtensionStack = () => { | ||
| // Extension-only subset: omits InsideStack callbacks/params the share extension never provides. | ||
| type ShareViewParams = Omit<InsideStackParamList['ShareView'], 'thread' | 'action' | 'finishShareView' | 'startShareView'>; | ||
|
|
||
| // withNavigation(x as any) returns ComponentType<{}> — not assignable to ComponentType<StaticScreenProps<T>> — due to the | ||
| // type cycle: these components reference ShareInsideStackParamList ← StaticParamList<typeof ShareExtension> ← these components. | ||
| const ShareListViewScreen: ComponentType<StaticScreenProps<undefined>> = withNavigation(ShareListView as any) as any; | ||
| const ShareViewScreen: ComponentType<StaticScreenProps<ShareViewParams>> = withNavigation(ShareView as any) as any; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slop (moderate) / altitude — foundation note, not a blocker. The trailing |
||
|
|
||
| const ShareExtension = createNativeStackNavigator({ | ||
| screenOptions: defaultHeader, | ||
| screens: { | ||
| ShareListView: ShareListViewScreen, | ||
| ShareView: ShareViewScreen, | ||
| SelectServerView | ||
| } | ||
| }).with(({ Navigator }) => { | ||
| 'use memo'; | ||
|
|
||
| const { theme } = useContext(ThemeContext); | ||
| return <Navigator screenOptions={themedHeader(theme)} />; | ||
| }); | ||
|
|
||
| export type ShareInsideStackParamList = StaticParamList<typeof ShareExtension>; | ||
|
|
||
| return ( | ||
| <ShareExtension.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}> | ||
| {/* @ts-ignore */} | ||
| <ShareExtension.Screen name='ShareListView' component={ShareListView} /> | ||
| {/* @ts-ignore */} | ||
| <ShareExtension.Screen name='ShareView' component={ShareView} /> | ||
| <ShareExtension.Screen name='SelectServerView' component={SelectServerView} /> | ||
| </ShareExtension.Navigator> | ||
| ); | ||
| }; | ||
| const ShareExtensionStack = ShareExtension.getComponent(); | ||
|
|
||
| export default ShareExtensionStack; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slop (low) — foundation note. Two things to be aware of before this HOC carries ~60 screens: (1)
navigation: any+ untypeduseNavigation()means the injectednavigationprop isanyat the boundary for every wrapped screen — acceptable for a class-component bridge, but it provides no nav typing downstream. (2) It doesn't forward refs. That's correct for React Navigation screens (the static API renders them viaMemoizedScreenwith only{ route }, never a ref), but worth a one-line comment that this is a screen-registration-only HOC so a future caller doesn't reach for it expecting ref passthrough. Otherwise the HOC is sound: stable identity (created once at registration),displayNameset, prop spread + navigation-last ordering correct.