Page MenuHomePhabricator

D7730.diff
No OneTemporary

D7730.diff

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

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)

Event Timeline