Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions app/definitions/navigationTypes.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -31,17 +31,3 @@ export type StackParamList = {
SetUsernameStack: NavigatorScreenParams<SetUsernameStackParamList>;
ShareExtensionStack: NavigatorScreenParams<ShareInsideStackParamList>;
};

export type ShareInsideStackParamList = {
ShareListView: undefined;
ShareView: {
attachments: IAttachment[];
isShareView?: boolean;
isShareExtension: boolean;
serverInfo: TServerModel;
text: string;
room: TSubscriptionModel;
thread?: any; // TODO: Change
};
SelectServerView: undefined;
};
14 changes: 14 additions & 0 deletions app/lib/navigation/withNavigation.tsx
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'>> {

Copy link
Copy Markdown
Member Author

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 + untyped useNavigation() means the injected navigation prop is any at 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 via MemoizedScreen with 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), displayName set, prop spread + navigation-last ordering correct.

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;
38 changes: 25 additions & 13 deletions app/stacks/ShareExtensionStack.tsx
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;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slop (moderate) / altitude — foundation note, not a blocker. The trailing as any discards exactly the param-type checking the explicit ComponentType<StaticScreenProps<ShareViewParams>> annotation is meant to give: ShareViewParams is now a hand-maintained duplicate of the route-param contract with no compile-time link to ShareView (whose route is typed against InsideStackParamList['ShareView']). If the two drift, nothing catches it. This is faithful to the old hand-written ShareInsideStackParamList, so it's fine for this slice — but it's the template the InsideStack (~60 screens) and MasterDetailStack slices will copy, so the lost type-safety compounds. The inner withNavigation(... as any) is defensible (silences the connect()/withTheme HOC prop types), but consider whether the outer as any can be avoided before scaling the pattern — e.g. a typed bridge so each screen's params stay verified against their source.


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;
Loading