Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3368157
D7730.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D7730.diff
View Options
diff --git a/native/invite-links/invite-links-navigator.react.js b/native/invite-links/invite-links-navigator.react.js
new file mode 100644
--- /dev/null
+++ b/native/invite-links/invite-links-navigator.react.js
@@ -0,0 +1,83 @@
+// @flow
+
+import {
+ createStackNavigator,
+ type StackNavigationHelpers,
+ type StackNavigationProp,
+} from '@react-navigation/stack';
+import * as React from 'react';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+import ViewInviteLinksHeaderLeftButton from './view-invite-links-header-left-button.react.js';
+import ViewInviteLinksHeaderTitle from './view-invite-links-header-title.react.js';
+import ViewInviteLinksScreen from './view-invite-links-screen.react.js';
+import { defaultStackScreenOptions } from '../navigation/options.js';
+import type { RootNavigationProp } from '../navigation/root-navigator.react.js';
+import {
+ type InviteLinkParamList,
+ ViewInviteLinksRouteName,
+ type ScreenParamList,
+} from '../navigation/route-names.js';
+import { useColors, useStyles } from '../themes/colors.js';
+
+const safeAreaEdges = ['bottom'];
+
+export type InviteLinksNavigationProps<
+ RouteName: $Keys<InviteLinkParamList> = $Keys<InviteLinkParamList>,
+> = StackNavigationProp<ScreenParamList, RouteName>;
+
+const InviteLinksStack = createStackNavigator<
+ ScreenParamList,
+ InviteLinkParamList,
+ StackNavigationHelpers<ScreenParamList>,
+>();
+
+const viewInviteLinksOptions = ({ route }) => ({
+ // eslint-disable-next-line react/display-name
+ headerTitle: props => (
+ <ViewInviteLinksHeaderTitle community={route.params.community} {...props} />
+ ),
+ headerLeft: ViewInviteLinksHeaderLeftButton,
+ headerBackImage: () => null,
+ headerBackTitleStyle: { marginLeft: 20 },
+});
+
+type Props = {
+ +navigation: RootNavigationProp<'InviteLinkNavigator'>,
+ ...
+};
+// eslint-disable-next-line no-unused-vars
+function InviteLinksNavigator(props: Props): React.Node {
+ const styles = useStyles(unboundStyles);
+ const colors = useColors();
+ const screenOptions = React.useMemo(
+ () => ({
+ ...defaultStackScreenOptions,
+ headerStyle: {
+ backgroundColor: colors.modalBackground,
+ borderBottomWidth: 1,
+ },
+ }),
+ [colors.modalBackground],
+ );
+ return (
+ <SafeAreaView style={styles.container} edges={safeAreaEdges}>
+ <InviteLinksStack.Navigator screenOptions={screenOptions}>
+ <InviteLinksStack.Screen
+ name={ViewInviteLinksRouteName}
+ component={ViewInviteLinksScreen}
+ options={viewInviteLinksOptions}
+ />
+ </InviteLinksStack.Navigator>
+ </SafeAreaView>
+ );
+}
+
+const unboundStyles = {
+ container: {
+ flex: 1,
+ backgroundColor: 'modalBackground',
+ },
+};
+
+export default InviteLinksNavigator;
diff --git a/native/invite-links/view-invite-links-header-left-button.react.js b/native/invite-links/view-invite-links-header-left-button.react.js
new file mode 100644
--- /dev/null
+++ b/native/invite-links/view-invite-links-header-left-button.react.js
@@ -0,0 +1,19 @@
+// @flow
+
+import { HeaderBackButton as BaseHeaderBackButton } from '@react-navigation/elements';
+import * as React from 'react';
+
+import { useColors } from '../themes/colors.js';
+
+type Props = React.ElementConfig<typeof BaseHeaderBackButton>;
+function ViewInviteLinksHeaderLeftButton(props: Props): React.Node {
+ const { headerChevron } = useColors();
+ if (!props.canGoBack) {
+ return null;
+ }
+ return (
+ <BaseHeaderBackButton {...props} label="Close" tintColor={headerChevron} />
+ );
+}
+
+export default ViewInviteLinksHeaderLeftButton;
diff --git a/native/invite-links/view-invite-links-header-title.react.js b/native/invite-links/view-invite-links-header-title.react.js
new file mode 100644
--- /dev/null
+++ b/native/invite-links/view-invite-links-header-title.react.js
@@ -0,0 +1,25 @@
+// @flow
+
+import type { HeaderTitleInputProps } from '@react-navigation/elements';
+import { HeaderTitle } from '@react-navigation/elements';
+import * as React from 'react';
+
+import type { ThreadInfo } from 'lib/types/thread-types.js';
+import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js';
+import { firstLine } from 'lib/utils/string-utils.js';
+
+type Props = {
+ +community: ThreadInfo,
+ ...HeaderTitleInputProps,
+};
+function ViewInviteLinksHeaderTitle(props: Props): React.Node {
+ const { community, ...rest } = props;
+ const { uiName } = useResolvedThreadInfo(community);
+ const title = `Invite people to ${firstLine(uiName)}`;
+ return <HeaderTitle {...rest}>{title}</HeaderTitle>;
+}
+
+const MemoizedViewInviteLinksHeaderTitle: React.ComponentType<Props> =
+ React.memo<Props>(ViewInviteLinksHeaderTitle);
+
+export default MemoizedViewInviteLinksHeaderTitle;
diff --git a/native/invite-links/view-invite-links-screen.react.js b/native/invite-links/view-invite-links-screen.react.js
new file mode 100644
--- /dev/null
+++ b/native/invite-links/view-invite-links-screen.react.js
@@ -0,0 +1,126 @@
+// @flow
+
+import Clipboard from '@react-native-clipboard/clipboard';
+import * as React from 'react';
+import { Text, View } from 'react-native';
+import { TouchableOpacity } from 'react-native-gesture-handler';
+
+import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js';
+import type { InviteLink } from 'lib/types/link-types.js';
+import type { ThreadInfo } from 'lib/types/thread-types.js';
+
+import SWMansionIcon from '../components/swmansion-icon.react.js';
+import { displayActionResultModal } from '../navigation/action-result-modal.js';
+import type { RootNavigationProp } from '../navigation/root-navigator.react.js';
+import type { NavigationRoute } from '../navigation/route-names.js';
+import { useSelector } from '../redux/redux-utils.js';
+import { useStyles, useColors } from '../themes/colors.js';
+
+export type ViewInviteLinksScreenParams = {
+ +community: ThreadInfo,
+};
+
+type Props = {
+ +navigation: RootNavigationProp<'ViewInviteLinks'>,
+ +route: NavigationRoute<'ViewInviteLinks'>,
+};
+
+const confirmCopy = () => displayActionResultModal('copied!');
+
+function ViewInviteLinksScreen(props: Props): React.Node {
+ const { community } = props.route.params;
+ const inviteLink: ?InviteLink = useSelector(primaryInviteLinksSelector)[
+ community.id
+ ];
+
+ const styles = useStyles(unboundStyles);
+ const { modalForegroundLabel } = useColors();
+ const linkUrl = `https://comm.app/invite/${inviteLink?.name ?? ''}`;
+ const onPressCopy = React.useCallback(() => {
+ Clipboard.setString(linkUrl);
+ setTimeout(confirmCopy);
+ }, [linkUrl]);
+
+ let publicLinkSection = null;
+ if (inviteLink) {
+ publicLinkSection = (
+ <>
+ <Text style={styles.sectionTitle}>PUBLIC LINK</Text>
+ <View style={styles.section}>
+ <TouchableOpacity style={styles.link} onPress={onPressCopy}>
+ <Text style={styles.linkText}>{linkUrl}</Text>
+ <View style={styles.button}>
+ <SWMansionIcon
+ name="link"
+ size={24}
+ color={modalForegroundLabel}
+ />
+ <Text style={styles.copy}>Copy</Text>
+ </View>
+ </TouchableOpacity>
+ <Text style={styles.details}>
+ Use this public link to invite your friends into the community!
+ </Text>
+ </View>
+ </>
+ );
+ }
+ return <View style={styles.container}>{publicLinkSection}</View>;
+}
+
+const unboundStyles = {
+ container: {
+ flex: 1,
+ paddingTop: 24,
+ },
+ sectionTitle: {
+ fontSize: 12,
+ fontWeight: '400',
+ lineHeight: 18,
+ color: 'modalBackgroundLabel',
+ paddingHorizontal: 16,
+ paddingBottom: 4,
+ },
+ section: {
+ borderBottomColor: 'modalSeparator',
+ borderBottomWidth: 1,
+ borderTopColor: 'modalSeparator',
+ borderTopWidth: 1,
+ backgroundColor: 'modalForeground',
+ padding: 16,
+ },
+ link: {
+ paddingHorizontal: 16,
+ paddingVertical: 9,
+ backgroundColor: 'inviteLinkButtonBackground',
+ borderRadius: 20,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ linkText: {
+ fontSize: 14,
+ fontWeight: '400',
+ lineHeight: 22,
+ color: 'inviteLinkLinkColor',
+ },
+ button: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ copy: {
+ fontSize: 12,
+ fontWeight: '400',
+ lineHeight: 18,
+ color: 'modalForegroundLabel',
+ paddingLeft: 8,
+ },
+ details: {
+ fontSize: 12,
+ fontWeight: '400',
+ lineHeight: 18,
+ color: 'modalForegroundLabel',
+ paddingTop: 16,
+ },
+};
+
+export default ViewInviteLinksScreen;
diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js
--- a/native/navigation/root-navigator.react.js
+++ b/native/navigation/root-navigator.react.js
@@ -41,6 +41,7 @@
TermsAndPrivacyRouteName,
RegistrationRouteName,
InviteLinkModalRouteName,
+ InviteLinkNavigatorRouteName,
} from './route-names.js';
import LoggedOutModal from '../account/logged-out-modal.react.js';
import RegistrationNavigator from '../account/registration/registration-navigator.react.js';
@@ -53,6 +54,7 @@
import ComposeSubchannelModal from '../chat/settings/compose-subchannel-modal.react.js';
import SidebarListModal from '../chat/sidebar-list-modal.react.js';
import SubchannelsListModal from '../chat/subchannels-list-modal.react.js';
+import InviteLinksNavigator from '../invite-links/invite-links-navigator.react.js';
import CustomServerModal from '../profile/custom-server-modal.react.js';
enableScreens();
@@ -210,6 +212,11 @@
component={InviteLinkModal}
options={modalOverlayScreenOptions}
/>
+ <Root.Screen
+ name={InviteLinkNavigatorRouteName}
+ component={InviteLinksNavigator}
+ options={modalOverlayScreenOptions}
+ />
<Root.Screen
name={ThreadPickerModalRouteName}
component={ThreadPickerModal}
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -29,6 +29,7 @@
import type { SubchannelListModalParams } from '../chat/subchannels-list-modal.react.js';
import type { TextMessageTooltipModalParams } from '../chat/text-message-tooltip-modal.react.js';
import type { TogglePinModalParams } from '../chat/toggle-pin-modal.react.js';
+import type { ViewInviteLinksScreenParams } from '../invite-links/view-invite-links-screen.react.js';
import type { ChatCameraModalParams } from '../media/chat-camera-modal.react.js';
import type { ImageModalParams } from '../media/image-modal.react.js';
import type { ThreadAvatarCameraModalParams } from '../media/thread-avatar-camera-modal.react.js';
@@ -68,6 +69,7 @@
export const ImageModalRouteName = 'ImageModal';
export const ImagePasteModalRouteName = 'ImagePasteModal';
export const InviteLinkModalRouteName = 'InviteLinkModal';
+export const InviteLinkNavigatorRouteName = 'InviteLinkNavigator';
export const LoggedOutModalRouteName = 'LoggedOutModal';
export const MessageListRouteName = 'MessageList';
export const MessageReactionsModalRouteName = 'MessageReactionsModal';
@@ -93,6 +95,7 @@
export const UserAvatarCameraModalRouteName = 'UserAvatarCameraModal';
export const TogglePinModalRouteName = 'TogglePinModal';
export const VideoPlaybackModalRouteName = 'VideoPlaybackModal';
+export const ViewInviteLinksRouteName = 'ViewInviteLinks';
export const TermsAndPrivacyRouteName = 'TermsAndPrivacyModal';
export const RegistrationRouteName = 'Registration';
export const KeyserverSelectionRouteName = 'KeyserverSelection';
@@ -117,6 +120,7 @@
+MessageReactionsModal: MessageReactionsModalParams,
+Registration: void,
+InviteLinkModal: InviteLinkModalParams,
+ +InviteLinkNavigator: void,
};
export type MessageTooltipRouteNames =
@@ -197,6 +201,10 @@
+UsernameSelection: UsernameSelectionParams,
};
+export type InviteLinkParamList = {
+ +ViewInviteLinks: ViewInviteLinksScreenParams,
+};
+
export type ScreenParamList = {
...RootParamList,
...OverlayParamList,
@@ -206,6 +214,7 @@
...ProfileParamList,
...CommunityDrawerParamList,
...RegistrationParamList,
+ ...InviteLinkParamList,
};
export type NavigationRoute<RouteName: string = $Keys<ScreenParamList>> =
diff --git a/native/themes/colors.js b/native/themes/colors.js
--- a/native/themes/colors.js
+++ b/native/themes/colors.js
@@ -139,6 +139,8 @@
messageLabel: designSystemColors.shadesBlack100,
modalSeparator: designSystemColors.shadesWhite60,
secondaryButtonBorder: designSystemColors.shadesWhite100,
+ inviteLinkLinkColor: designSystemColors.shadesBlack100,
+ inviteLinkButtonBackground: designSystemColors.shadesWhite60,
});
export type Colors = $Exact<typeof light>;
@@ -234,6 +236,8 @@
messageLabel: designSystemColors.shadesWhite60,
modalSeparator: designSystemColors.shadesBlack80,
secondaryButtonBorder: designSystemColors.shadesWhite100,
+ inviteLinkLinkColor: designSystemColors.shadesWhite80,
+ inviteLinkButtonBackground: designSystemColors.shadesBlack80,
});
const colors = { light, dark };
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Nov 26, 6:25 PM (21 h, 37 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2585748
Default Alt Text
D7730.diff (12 KB)
Attached To
Mode
D7730: [native] Create a modal where invite links are displayed
Attached
Detach File
Event Timeline
Log In to Comment