From 4cd663cbdb730947b80cda0e0463f2eb3a37fe78 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 18 Jun 2026 22:22:47 -0300 Subject: [PATCH 1/2] refactor: migrate ShareExtensionStack to React Navigation static config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converts ShareExtensionStack from the dynamic JSX API to the static config API (createNativeStackNavigator with screens object). Introduces a shared withNavigation HOC in app/lib/navigation/ that injects navigation={useNavigation()} at the screen registration site, allowing class components (ShareListView, ShareView) to keep consuming this.props.navigation without internal edits. - ShareListView and ShareView are wrapped via withNavigation() at registration; cast through `any` to break the type cycle that would arise from their NativeStackNavigationProp props referencing StaticParamList ← themselves. - ShareViewParams is declared inline so StaticParamList correctly derives ShareView route params from the explicit ComponentType annotation. - ShareInsideStackParamList is now derived via StaticParamList and re-exported from navigationTypes.ts; the hand-written definition and its IAttachment/TServerModel/TSubscriptionModel imports are removed. - Theme spread moves into the .with() callback (themedHeader applied via Navigator screenOptions); defaultHeader stays in the static config. - Default export is ShareExtension.getComponent() so AppContainer is untouched during this slice. Claude-Session: https://claude.ai/code/session_01Qh9P8Bbp6V7PFpCY2X5on3 --- app/definitions/navigationTypes.ts | 20 ++---------- app/lib/navigation/withNavigation.tsx | 13 ++++++++ app/stacks/ShareExtensionStack.tsx | 45 +++++++++++++++++++-------- 3 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 app/lib/navigation/withNavigation.tsx diff --git a/app/definitions/navigationTypes.ts b/app/definitions/navigationTypes.ts index 7d6fe2eed98..246c2353a4d 100644 --- a/app/definitions/navigationTypes.ts +++ b/app/definitions/navigationTypes.ts @@ -1,11 +1,11 @@ import { type NavigatorScreenParams } from '@react-navigation/core'; import { type NativeStackNavigationOptions } from '@react-navigation/native-stack'; -import { type TSubscriptionModel } from './ISubscription'; -import { type TServerModel } from './IServer'; -import { type IAttachment } from './IAttachment'; import { type MasterDetailInsideStackParamList } from '../stacks/MasterDetailStack/types'; import { type OutsideParamList, type InsideStackParamList } from '../stacks/types'; +import { type ShareInsideStackParamList } from '../stacks/ShareExtensionStack'; + +export type { ShareInsideStackParamList }; interface INavigationProps { route?: any; @@ -31,17 +31,3 @@ export type StackParamList = { SetUsernameStack: NavigatorScreenParams; ShareExtensionStack: NavigatorScreenParams; }; - -export type ShareInsideStackParamList = { - ShareListView: undefined; - ShareView: { - attachments: IAttachment[]; - isShareView?: boolean; - isShareExtension: boolean; - serverInfo: TServerModel; - text: string; - room: TSubscriptionModel; - thread?: any; // TODO: Change - }; - SelectServerView: undefined; -}; diff --git a/app/lib/navigation/withNavigation.tsx b/app/lib/navigation/withNavigation.tsx new file mode 100644 index 00000000000..12367faca4e --- /dev/null +++ b/app/lib/navigation/withNavigation.tsx @@ -0,0 +1,13 @@ +import { useNavigation } from '@react-navigation/native'; +import { type ComponentType } from 'react'; + +function withNavigation

(WrappedComponent: ComponentType

): ComponentType> { + const WithNavigation = (props: Omit) => { + const navigation = useNavigation(); + return ; + }; + WithNavigation.displayName = `WithNavigation(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; + return WithNavigation; +} + +export default withNavigation; diff --git a/app/stacks/ShareExtensionStack.tsx b/app/stacks/ShareExtensionStack.tsx index 0236e244b1f..ed711b9639c 100644 --- a/app/stacks/ShareExtensionStack.tsx +++ b/app/stacks/ShareExtensionStack.tsx @@ -1,27 +1,46 @@ -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 IAttachment, type TServerModel, type TSubscriptionModel } from '../definitions'; -const ShareExtension = createNativeStackNavigator(); -const ShareExtensionStack = () => { +type ShareViewParams = { + attachments: IAttachment[]; + isShareView?: boolean; + isShareExtension: boolean; + serverInfo: TServerModel; + text: string; + room: TSubscriptionModel; + thread?: any; +}; + +// Cast through `any` to break the type cycle that would arise from ShareListView/ShareView +// referencing ShareInsideStackParamList ← StaticParamList ← these components. +const ShareListViewScreen: ComponentType> = withNavigation(ShareListView as any) as any; +const ShareViewScreen: ComponentType> = withNavigation(ShareView as any) as any; + +const ShareExtension = createNativeStackNavigator({ + screenOptions: defaultHeader, + screens: { + ShareListView: ShareListViewScreen, + ShareView: ShareViewScreen, + SelectServerView + } +}).with(({ Navigator }) => { 'use memo'; const { theme } = useContext(ThemeContext); + return ; +}); - return ( - - {/* @ts-ignore */} - - {/* @ts-ignore */} - - - - ); -}; +export type ShareInsideStackParamList = StaticParamList; + +const ShareExtensionStack = ShareExtension.getComponent(); export default ShareExtensionStack; From a6593bc777e39fb4fa3aef3632fa89d6b783d482 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 19 Jun 2026 15:07:42 -0300 Subject: [PATCH 2/2] refactor: tighten ShareExtensionStack screen typing Derive ShareViewParams from InsideStackParamList['ShareView'] (omitting the callbacks/params the share extension never provides) so any drift in the source param shape is caught at compile time instead of silently diverging from a hand-maintained duplicate. Document withNavigation as a screen-registration-only HOC that injects navigation via hook and forwards no refs, and explain why the outer screen casts are load-bearing (the static-config param-list type cycle). Claude-Session: https://claude.ai/code/session_01FambjUT5Y47f8V6Kc9Fpn5 --- app/lib/navigation/withNavigation.tsx | 1 + app/stacks/ShareExtensionStack.tsx | 21 +++++++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/lib/navigation/withNavigation.tsx b/app/lib/navigation/withNavigation.tsx index 12367faca4e..d1bc984f257 100644 --- a/app/lib/navigation/withNavigation.tsx +++ b/app/lib/navigation/withNavigation.tsx @@ -1,6 +1,7 @@ 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

(WrappedComponent: ComponentType

): ComponentType> { const WithNavigation = (props: Omit) => { const navigation = useNavigation(); diff --git a/app/stacks/ShareExtensionStack.tsx b/app/stacks/ShareExtensionStack.tsx index ed711b9639c..8053c367529 100644 --- a/app/stacks/ShareExtensionStack.tsx +++ b/app/stacks/ShareExtensionStack.tsx @@ -8,20 +8,13 @@ import SelectServerView from '../views/SelectServerView'; import ShareListView from '../views/ShareListView'; import ShareView from '../views/ShareView'; import withNavigation from '../lib/navigation/withNavigation'; -import { type IAttachment, type TServerModel, type TSubscriptionModel } from '../definitions'; - -type ShareViewParams = { - attachments: IAttachment[]; - isShareView?: boolean; - isShareExtension: boolean; - serverInfo: TServerModel; - text: string; - room: TSubscriptionModel; - thread?: any; -}; - -// Cast through `any` to break the type cycle that would arise from ShareListView/ShareView -// referencing ShareInsideStackParamList ← StaticParamList ← these components. +import { type InsideStackParamList } from './types'; + +// Extension-only subset: omits InsideStack callbacks/params the share extension never provides. +type ShareViewParams = Omit; + +// withNavigation(x as any) returns ComponentType<{}> — not assignable to ComponentType> — due to the +// type cycle: these components reference ShareInsideStackParamList ← StaticParamList ← these components. const ShareListViewScreen: ComponentType> = withNavigation(ShareListView as any) as any; const ShareViewScreen: ComponentType> = withNavigation(ShareView as any) as any;