diff --git a/native/navigation/invite-link-modal.react.js b/native/navigation/invite-link-modal.react.js
new file mode 100644
index 000000000..7bb3decad
--- /dev/null
+++ b/native/navigation/invite-link-modal.react.js
@@ -0,0 +1,174 @@
+// @flow
+
+import * as React from 'react';
+import { View, Text } from 'react-native';
+
+import type { InviteLinkVerificationResponse } from 'lib/types/link-types.js';
+
+import type { RootNavigationProp } from './root-navigator.react.js';
+import type { NavigationRoute } from './route-names.js';
+import Button from '../components/button.react.js';
+import Modal from '../components/modal.react.js';
+import { useStyles } from '../themes/colors.js';
+
+export type InviteLinkModalParams = {
+ +invitationDetails: InviteLinkVerificationResponse,
+ +secret: string,
+};
+
+type Props = {
+ +navigation: RootNavigationProp<'InviteLinkModal'>,
+ +route: NavigationRoute<'InviteLinkModal'>,
+};
+
+function InviteLinkModal(props: Props): React.Node {
+ const styles = useStyles(unboundStyles);
+ const { invitationDetails } = props.route.params;
+
+ React.useEffect(() => {
+ if (invitationDetails.status === 'already_joined') {
+ props.navigation.goBack();
+ }
+ }, [invitationDetails.status, props.navigation]);
+
+ const header = React.useMemo(() => {
+ if (invitationDetails.status === 'valid') {
+ return (
+ <>
+ You have been invited to join
+
+ {invitationDetails.community.name}
+
+ >
+ );
+ }
+ return (
+ <>
+ Invite invalid
+
+ This invite link may be expired, please try again with another invite
+ link
+
+ >
+ );
+ }, [
+ invitationDetails,
+ styles.communityName,
+ styles.invalidInviteExplanation,
+ styles.invalidInviteTitle,
+ styles.invitation,
+ ]);
+
+ const buttons = React.useMemo(() => {
+ if (invitationDetails.status === 'valid') {
+ return (
+ <>
+
+
+ >
+ );
+ }
+ return (
+
+ );
+ }, [
+ invitationDetails.status,
+ props.navigation.goBack,
+ styles.button,
+ styles.buttonPrimary,
+ styles.buttonSecondary,
+ styles.buttonText,
+ styles.gap,
+ ]);
+
+ return (
+
+ {header}
+
+ {buttons}
+
+ );
+}
+
+const unboundStyles = {
+ modal: {
+ backgroundColor: 'modalForeground',
+ paddingVertical: 24,
+ paddingHorizontal: 16,
+ flex: 0,
+ },
+ invitation: {
+ color: 'whiteText',
+ textAlign: 'center',
+ fontSize: 14,
+ fontWeight: '400',
+ lineHeight: 22,
+ marginBottom: 24,
+ },
+ communityName: {
+ color: 'whiteText',
+ textAlign: 'center',
+ fontSize: 18,
+ fontWeight: '500',
+ lineHeight: 24,
+ },
+ invalidInviteTitle: {
+ color: 'whiteText',
+ textAlign: 'center',
+ fontSize: 22,
+ fontWeight: '500',
+ lineHeight: 28,
+ marginBottom: 24,
+ },
+ invalidInviteExplanation: {
+ color: 'whiteText',
+ textAlign: 'center',
+ fontSize: 14,
+ fontWeight: '400',
+ lineHeight: 22,
+ },
+ separator: {
+ height: 1,
+ backgroundColor: 'modalSeparator',
+ marginVertical: 24,
+ },
+ gap: {
+ marginBottom: 16,
+ },
+ button: {
+ borderRadius: 8,
+ paddingVertical: 12,
+ paddingHorizontal: 24,
+ },
+ buttonPrimary: {
+ backgroundColor: 'purpleButton',
+ },
+ buttonSecondary: {
+ borderColor: 'secondaryButtonBorder',
+ borderWidth: 1,
+ },
+ buttonText: {
+ color: 'whiteText',
+ textAlign: 'center',
+ fontSize: 16,
+ fontWeight: '500',
+ lineHeight: 24,
+ },
+};
+
+export default InviteLinkModal;
diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js
index f2cf8e0d2..48a63404e 100644
--- a/native/navigation/root-navigator.react.js
+++ b/native/navigation/root-navigator.react.js
@@ -1,254 +1,261 @@
// @flow
import {
createNavigatorFactory,
useNavigationBuilder,
type StackNavigationState,
type StackOptions,
type StackNavigationEventMap,
type StackNavigatorProps,
type ExtraStackNavigatorProps,
type ParamListBase,
type StackNavigationHelpers,
type StackNavigationProp,
} from '@react-navigation/native';
import { StackView, TransitionPresets } from '@react-navigation/stack';
import * as React from 'react';
import { Platform } from 'react-native';
import { enableScreens } from 'react-native-screens';
import AppNavigator from './app-navigator.react.js';
+import InviteLinkModal from './invite-link-modal.react.js';
import { defaultStackScreenOptions } from './options.js';
import { RootNavigatorContext } from './root-navigator-context.js';
import RootRouter, {
type RootRouterExtraNavigationHelpers,
} from './root-router.js';
import {
LoggedOutModalRouteName,
AppRouteName,
ThreadPickerModalRouteName,
ImagePasteModalRouteName,
AddUsersModalRouteName,
CustomServerModalRouteName,
ColorSelectorModalRouteName,
ComposeSubchannelModalRouteName,
SidebarListModalRouteName,
SubchannelsListModalRouteName,
MessageReactionsModalRouteName,
type ScreenParamList,
type RootParamList,
TermsAndPrivacyRouteName,
RegistrationRouteName,
+ InviteLinkModalRouteName,
} from './route-names.js';
import LoggedOutModal from '../account/logged-out-modal.react.js';
import RegistrationNavigator from '../account/registration/registration-navigator.react.js';
import TermsAndPrivacyModal from '../account/terms-and-privacy-modal.react.js';
import ThreadPickerModal from '../calendar/thread-picker-modal.react.js';
import ImagePasteModal from '../chat/image-paste-modal.react.js';
import MessageReactionsModal from '../chat/message-reactions-modal.react.js';
import AddUsersModal from '../chat/settings/add-users-modal.react.js';
import ColorSelectorModal from '../chat/settings/color-selector-modal.react.js';
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 CustomServerModal from '../profile/custom-server-modal.react.js';
enableScreens();
export type RootNavigationHelpers = {
...$Exact>,
...RootRouterExtraNavigationHelpers,
...
};
type RootNavigatorProps = StackNavigatorProps>;
function RootNavigator({
initialRouteName,
children,
screenOptions,
defaultScreenOptions,
screenListeners,
id,
...rest
}: RootNavigatorProps) {
const [keyboardHandlingEnabled, setKeyboardHandlingEnabled] =
React.useState(true);
const mergedScreenOptions = React.useMemo(() => {
if (typeof screenOptions === 'function') {
return input => ({
...screenOptions(input),
keyboardHandlingEnabled,
});
}
return {
...screenOptions,
keyboardHandlingEnabled,
};
}, [screenOptions, keyboardHandlingEnabled]);
const { state, descriptors, navigation } = useNavigationBuilder(RootRouter, {
id,
initialRouteName,
children,
screenOptions: mergedScreenOptions,
defaultScreenOptions,
screenListeners,
});
const rootNavigationContext = React.useMemo(
() => ({ setKeyboardHandlingEnabled }),
[setKeyboardHandlingEnabled],
);
return (
);
}
const createRootNavigator = createNavigatorFactory<
StackNavigationState,
StackOptions,
StackNavigationEventMap,
RootNavigationHelpers<>,
ExtraStackNavigatorProps,
>(RootNavigator);
const baseTransitionPreset = Platform.select({
ios: TransitionPresets.ModalSlideFromBottomIOS,
default: TransitionPresets.FadeFromBottomAndroid,
});
const transitionPreset = {
...baseTransitionPreset,
cardStyleInterpolator: interpolatorProps => {
const baseCardStyleInterpolator =
baseTransitionPreset.cardStyleInterpolator(interpolatorProps);
const overlayOpacity = interpolatorProps.current.progress.interpolate({
inputRange: [0, 1],
outputRange: ([0, 0.7]: number[]), // Flow...
extrapolate: 'clamp',
});
return {
...baseCardStyleInterpolator,
overlayStyle: [
baseCardStyleInterpolator.overlayStyle,
{ opacity: overlayOpacity },
],
};
},
};
const defaultScreenOptions = {
...defaultStackScreenOptions,
...transitionPreset,
cardStyle: { backgroundColor: 'transparent' },
presentation: 'modal',
headerShown: false,
};
const disableGesturesScreenOptions = {
gestureEnabled: false,
};
const modalOverlayScreenOptions = {
cardOverlayEnabled: true,
presentation: 'transparentModal',
};
const termsAndPrivacyModalScreenOptions = {
gestureEnabled: false,
cardOverlayEnabled: true,
presentation: 'transparentModal',
};
export type RootRouterNavigationProp<
ParamList: ParamListBase = ParamListBase,
RouteName: $Keys = $Keys,
> = {
...StackNavigationProp,
...RootRouterExtraNavigationHelpers,
};
export type RootNavigationProp<
RouteName: $Keys = $Keys,
> = {
...StackNavigationProp,
...RootRouterExtraNavigationHelpers,
};
const Root = createRootNavigator<
ScreenParamList,
RootParamList,
RootNavigationHelpers,
>();
function RootComponent(): React.Node {
return (
+
);
}
export default RootComponent;
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
index 7a2fe0f58..042a54ff6 100644
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -1,202 +1,205 @@
// @flow
import type { RouteProp } from '@react-navigation/native';
import type { ActionResultModalParams } from './action-result-modal.react.js';
+import type { InviteLinkModalParams } from './invite-link-modal.react';
import type { TermsAndPrivacyModalParams } from '../account/terms-and-privacy-modal.react.js';
import type { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react.js';
import type { ComposeSubchannelParams } from '../chat/compose-subchannel.react.js';
import type { FullScreenThreadMediaGalleryParams } from '../chat/fullscreen-thread-media-gallery.react.js';
import type { ImagePasteModalParams } from '../chat/image-paste-modal.react.js';
import type { MessageListParams } from '../chat/message-list-types.js';
import type { MessageReactionsModalParams } from '../chat/message-reactions-modal.react.js';
import type { MultimediaMessageTooltipModalParams } from '../chat/multimedia-message-tooltip-modal.react.js';
import type { RobotextMessageTooltipModalParams } from '../chat/robotext-message-tooltip-modal.react.js';
import type { AddUsersModalParams } from '../chat/settings/add-users-modal.react.js';
import type { ColorSelectorModalParams } from '../chat/settings/color-selector-modal.react.js';
import type { ComposeSubchannelModalParams } from '../chat/settings/compose-subchannel-modal.react.js';
import type { DeleteThreadParams } from '../chat/settings/delete-thread.react.js';
import type { EmojiThreadAvatarCreationParams } from '../chat/settings/emoji-thread-avatar-creation.react.js';
import type { ThreadSettingsMemberTooltipModalParams } from '../chat/settings/thread-settings-member-tooltip-modal.react.js';
import type { ThreadSettingsParams } from '../chat/settings/thread-settings.react.js';
import type { SidebarListModalParams } from '../chat/sidebar-list-modal.react.js';
import type { SubchannelListModalParams } from '../chat/subchannels-list-modal.react.js';
import type { TextMessageTooltipModalParams } from '../chat/text-message-tooltip-modal.react.js';
import type { ChatCameraModalParams } from '../media/chat-camera-modal.react.js';
import type { ImageModalParams } from '../media/image-modal.react.js';
import type { VideoPlaybackModalParams } from '../media/video-playback-modal.react.js';
import type { CustomServerModalParams } from '../profile/custom-server-modal.react.js';
import type { RelationshipListItemTooltipModalParams } from '../profile/relationship-list-item-tooltip-modal.react.js';
export const ActionResultModalRouteName = 'ActionResultModal';
export const AddUsersModalRouteName = 'AddUsersModal';
export const AppearancePreferencesRouteName = 'AppearancePreferences';
export const AppRouteName = 'App';
export const AppsRouteName = 'Apps';
export const BackgroundChatThreadListRouteName = 'BackgroundChatThreadList';
export const BlockListRouteName = 'BlockList';
export const BuildInfoRouteName = 'BuildInfo';
export const CalendarRouteName = 'Calendar';
export const ChatCameraModalRouteName = 'ChatCameraModal';
export const ChatRouteName = 'Chat';
export const ChatThreadListRouteName = 'ChatThreadList';
export const ColorSelectorModalRouteName = 'ColorSelectorModal';
export const ComposeSubchannelModalRouteName = 'ComposeSubchannelModal';
export const ComposeSubchannelRouteName = 'ComposeSubchannel';
export const CommunityDrawerNavigatorRouteName = 'CommunityDrawerNavigator';
export const CustomServerModalRouteName = 'CustomServerModal';
export const DefaultNotificationsPreferencesRouteName = 'DefaultNotifications';
export const DeleteAccountRouteName = 'DeleteAccount';
export const DeleteThreadRouteName = 'DeleteThread';
export const DevToolsRouteName = 'DevTools';
export const EditPasswordRouteName = 'EditPassword';
export const EmojiThreadAvatarCreationRouteName = 'EmojiThreadAvatarCreation';
export const EmojiUserAvatarCreationRouteName = 'EmojiUserAvatarCreation';
export const FriendListRouteName = 'FriendList';
export const FullScreenThreadMediaGalleryRouteName =
'FullScreenThreadMediaGallery';
export const HomeChatThreadListRouteName = 'HomeChatThreadList';
export const ImageModalRouteName = 'ImageModal';
export const ImagePasteModalRouteName = 'ImagePasteModal';
+export const InviteLinkModalRouteName = 'InviteLinkModal';
export const LoggedOutModalRouteName = 'LoggedOutModal';
export const MessageListRouteName = 'MessageList';
export const MessageReactionsModalRouteName = 'MessageReactionsModal';
export const MultimediaMessageTooltipModalRouteName =
'MultimediaMessageTooltipModal';
export const PrivacyPreferencesRouteName = 'PrivacyPreferences';
export const ProfileRouteName = 'Profile';
export const ProfileScreenRouteName = 'ProfileScreen';
export const RelationshipListItemTooltipModalRouteName =
'RelationshipListItemTooltipModal';
export const RobotextMessageTooltipModalRouteName =
'RobotextMessageTooltipModal';
export const SidebarListModalRouteName = 'SidebarListModal';
export const SubchannelsListModalRouteName = 'SubchannelsListModal';
export const TabNavigatorRouteName = 'TabNavigator';
export const TextMessageTooltipModalRouteName = 'TextMessageTooltipModal';
export const ThreadPickerModalRouteName = 'ThreadPickerModal';
export const ThreadSettingsMemberTooltipModalRouteName =
'ThreadSettingsMemberTooltipModal';
export const ThreadSettingsRouteName = 'ThreadSettings';
export const VideoPlaybackModalRouteName = 'VideoPlaybackModal';
export const TermsAndPrivacyRouteName = 'TermsAndPrivacyModal';
export const RegistrationRouteName = 'Registration';
export const KeyserverSelectionRouteName = 'KeyserverSelection';
export type RootParamList = {
+LoggedOutModal: void,
+App: void,
+ThreadPickerModal: ThreadPickerModalParams,
+AddUsersModal: AddUsersModalParams,
+CustomServerModal: CustomServerModalParams,
+ColorSelectorModal: ColorSelectorModalParams,
+ComposeSubchannelModal: ComposeSubchannelModalParams,
+SidebarListModal: SidebarListModalParams,
+ImagePasteModal: ImagePasteModalParams,
+TermsAndPrivacyModal: TermsAndPrivacyModalParams,
+SubchannelsListModal: SubchannelListModalParams,
+MessageReactionsModal: MessageReactionsModalParams,
+Registration: void,
+ +InviteLinkModal: InviteLinkModalParams,
};
export type MessageTooltipRouteNames =
| typeof RobotextMessageTooltipModalRouteName
| typeof MultimediaMessageTooltipModalRouteName
| typeof TextMessageTooltipModalRouteName;
export type TooltipModalParamList = {
+MultimediaMessageTooltipModal: MultimediaMessageTooltipModalParams,
+TextMessageTooltipModal: TextMessageTooltipModalParams,
+ThreadSettingsMemberTooltipModal: ThreadSettingsMemberTooltipModalParams,
+RelationshipListItemTooltipModal: RelationshipListItemTooltipModalParams,
+RobotextMessageTooltipModal: RobotextMessageTooltipModalParams,
};
export type OverlayParamList = {
+CommunityDrawerNavigator: void,
+ImageModal: ImageModalParams,
+ActionResultModal: ActionResultModalParams,
+ChatCameraModal: ChatCameraModalParams,
+VideoPlaybackModal: VideoPlaybackModalParams,
...TooltipModalParamList,
};
export type TabParamList = {
+Calendar: void,
+Chat: void,
+Profile: void,
+Apps: void,
};
export type ChatParamList = {
+ChatThreadList: void,
+MessageList: MessageListParams,
+ComposeSubchannel: ComposeSubchannelParams,
+ThreadSettings: ThreadSettingsParams,
+EmojiThreadAvatarCreation: EmojiThreadAvatarCreationParams,
+DeleteThread: DeleteThreadParams,
+FullScreenThreadMediaGallery: FullScreenThreadMediaGalleryParams,
};
export type ChatTopTabsParamList = {
+HomeChatThreadList: void,
+BackgroundChatThreadList: void,
};
export type ProfileParamList = {
+ProfileScreen: void,
+EmojiUserAvatarCreation: void,
+EditPassword: void,
+DeleteAccount: void,
+BuildInfo: void,
+DevTools: void,
+AppearancePreferences: void,
+PrivacyPreferences: void,
+DefaultNotifications: void,
+FriendList: void,
+BlockList: void,
};
export type CommunityDrawerParamList = { +TabNavigator: void };
export type RegistrationParamList = { +KeyserverSelection: void };
export type ScreenParamList = {
...RootParamList,
...OverlayParamList,
...TabParamList,
...ChatParamList,
...ChatTopTabsParamList,
...ProfileParamList,
...CommunityDrawerParamList,
...RegistrationParamList,
};
export type NavigationRoute> =
RouteProp;
export const accountModals = [LoggedOutModalRouteName, RegistrationRouteName];
export const scrollBlockingModals = [
ImageModalRouteName,
MultimediaMessageTooltipModalRouteName,
TextMessageTooltipModalRouteName,
ThreadSettingsMemberTooltipModalRouteName,
RelationshipListItemTooltipModalRouteName,
RobotextMessageTooltipModalRouteName,
VideoPlaybackModalRouteName,
];
export const chatRootModals = [
AddUsersModalRouteName,
ColorSelectorModalRouteName,
ComposeSubchannelModalRouteName,
];
export const threadRoutes = [
MessageListRouteName,
ThreadSettingsRouteName,
DeleteThreadRouteName,
ComposeSubchannelRouteName,
FullScreenThreadMediaGalleryRouteName,
];
diff --git a/native/themes/colors.js b/native/themes/colors.js
index e9a2f2d2c..6f3f867fa 100644
--- a/native/themes/colors.js
+++ b/native/themes/colors.js
@@ -1,317 +1,321 @@
// @flow
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createSelector } from 'reselect';
import { selectBackgroundIsDark } from '../navigation/nav-selectors.js';
import { NavContext } from '../navigation/navigation-context.js';
import { useSelector } from '../redux/redux-utils.js';
import type { AppState } from '../redux/state-types.js';
import type { GlobalTheme } from '../types/themes.js';
const light = Object.freeze({
blockQuoteBackground: '#E0E0E0',
blockQuoteBorder: '#CCCCCC',
codeBackground: '#E0E0E0',
disabledButton: '#E0E0E0',
disconnectedBarBackground: '#F5F5F5',
editButton: '#A4A4A2',
floatingButtonBackground: '#999999',
floatingButtonLabel: '#EBEBEB',
headerChevron: '#0A0A0A',
inlineEngagementBackground: '#E0E0E0',
inlineEngagementLabel: '#0A0A0A',
link: '#7E57C2',
listBackground: '#FFFFFF',
listBackgroundLabel: '#0A0A0A',
listBackgroundSecondaryLabel: '#444444',
listBackgroundTernaryLabel: '#999999',
listChatBubble: '#F1F0F5',
listForegroundLabel: '#0A0A0A',
listForegroundSecondaryLabel: '#333333',
listForegroundTertiaryLabel: '#666666',
listInputBackground: '#F5F5F5',
listInputBar: '#E2E2E2',
listInputButton: '#8E8D92',
listIosHighlightUnderlay: '#DDDDDDDD',
listSearchBackground: '#F5F5F5',
listSearchIcon: '#8E8D92',
listSeparatorLabel: '#666666',
modalBackground: '#EBEBEB',
modalBackgroundLabel: '#333333',
modalBackgroundSecondaryLabel: '#AAAAAA',
modalButton: '#BBBBBB',
modalButtonLabel: '#0A0A0A',
modalContrastBackground: '#0A0A0A',
modalContrastForegroundLabel: '#FFFFFF',
modalContrastOpacity: 0.7,
modalForeground: '#FFFFFF',
modalForegroundBorder: '#CCCCCC',
modalForegroundLabel: '#0A0A0A',
modalForegroundSecondaryLabel: '#888888',
modalForegroundTertiaryLabel: '#AAAAAA',
modalIosHighlightUnderlay: '#CCCCCCDD',
modalSubtext: '#CCCCCC',
modalSubtextLabel: '#666666',
navigationCard: '#FFFFFF',
navigationChevron: '#CCCCCC',
panelBackground: '#F5F5F5',
panelBackgroundLabel: '#888888',
panelForeground: '#FFFFFF',
panelForegroundBorder: '#CCCCCC',
panelForegroundLabel: '#0A0A0A',
panelForegroundSecondaryLabel: '#333333',
panelForegroundTertiaryLabel: '#888888',
panelIosHighlightUnderlay: '#EBEBEBDD',
panelSecondaryForeground: '#F5F5F5',
panelSecondaryForegroundBorder: '#CCCCCC',
purpleLink: '#7E57C2',
purpleButton: '#7E57C2',
reactionSelectionPopoverItemBackground: '#404040',
redText: '#F53100',
spoiler: '#33332C',
tabBarAccent: '#7E57C2',
tabBarBackground: '#F5F5F5',
tabBarActiveTintColor: '#7E57C2',
vibrantGreenButton: '#00C853',
vibrantRedButton: '#F53100',
whiteText: '#FFFFFF',
tooltipBackground: '#E0E0E0',
logInSpacer: '#FFFFFF33',
siweButton: '#FFFFFF',
siweButtonText: '#1F1F1F',
drawerExpandButton: '#808080',
drawerExpandButtonDisabled: '#CCCCCC',
drawerItemLabelLevel0: '#0A0A0A',
drawerItemLabelLevel1: '#0A0A0A',
drawerItemLabelLevel2: '#1F1F1F',
drawerOpenCommunityBackground: '#F5F5F5',
drawerBackground: '#FFFFFF',
subthreadsModalClose: '#808080',
subthreadsModalBackground: '#EBEBEB',
subthreadsModalSearch: '#00000008',
messageLabel: '#0A0A0A',
+ modalSeparator: '#CCCCCC',
+ secondaryButtonBorder: '#FFFFFF',
});
export type Colors = $Exact;
const dark: Colors = Object.freeze({
blockQuoteBackground: '#A9A9A9',
blockQuoteBorder: '#808080',
codeBackground: '#0A0A0A',
disabledButton: '#404040',
disconnectedBarBackground: '#1F1F1F',
editButton: '#666666',
floatingButtonBackground: '#666666',
floatingButtonLabel: '#FFFFFF',
headerChevron: '#FFFFFF',
inlineEngagementBackground: '#666666',
inlineEngagementLabel: '#FFFFFF',
link: '#AE94DB',
listBackground: '#0A0A0A',
listBackgroundLabel: '#CCCCCC',
listBackgroundSecondaryLabel: '#BBBBBB',
listBackgroundTernaryLabel: '#808080',
listChatBubble: '#26252A',
listForegroundLabel: '#FFFFFF',
listForegroundSecondaryLabel: '#CCCCCC',
listForegroundTertiaryLabel: '#808080',
listInputBackground: '#1F1F1F',
listInputBar: '#666666',
listInputButton: '#CCCCCC',
listIosHighlightUnderlay: '#BBBBBB88',
listSearchBackground: '#1F1F1F',
listSearchIcon: '#CCCCCC',
listSeparatorLabel: '#EBEBEB',
modalBackground: '#0A0A0A',
modalBackgroundLabel: '#CCCCCC',
modalBackgroundSecondaryLabel: '#666666',
modalButton: '#666666',
modalButtonLabel: '#FFFFFF',
modalContrastBackground: '#FFFFFF',
modalContrastForegroundLabel: '#0A0A0A',
modalContrastOpacity: 0.85,
modalForeground: '#1F1F1F',
modalForegroundBorder: '#1F1F1F',
modalForegroundLabel: '#FFFFFF',
modalForegroundSecondaryLabel: '#AAAAAA',
modalForegroundTertiaryLabel: '#666666',
modalIosHighlightUnderlay: '#AAAAAA88',
modalSubtext: '#404040',
modalSubtextLabel: '#AAAAAA',
navigationCard: '#2A2A2A',
navigationChevron: '#666666',
panelBackground: '#0A0A0A',
panelBackgroundLabel: '#CCCCCC',
panelForeground: '#1F1F1F',
panelForegroundBorder: '#2C2C2E',
panelForegroundLabel: '#FFFFFF',
panelForegroundSecondaryLabel: '#CCCCCC',
panelForegroundTertiaryLabel: '#AAAAAA',
panelIosHighlightUnderlay: '#313035',
panelSecondaryForeground: '#333333',
panelSecondaryForegroundBorder: '#666666',
purpleLink: '#AE94DB',
purpleButton: '#7E57C2',
reactionSelectionPopoverItemBackground: '#404040',
redText: '#F53100',
spoiler: '#33332C',
tabBarAccent: '#AE94DB',
tabBarBackground: '#0A0A0A',
tabBarActiveTintColor: '#AE94DB',
vibrantGreenButton: '#00C853',
vibrantRedButton: '#F53100',
whiteText: '#FFFFFF',
tooltipBackground: '#1F1F1F',
logInSpacer: '#FFFFFF33',
siweButton: '#FFFFFF',
siweButtonText: '#1F1F1F',
drawerExpandButton: '#808080',
drawerExpandButtonDisabled: '#404040',
drawerItemLabelLevel0: '#CCCCCC',
drawerItemLabelLevel1: '#CCCCCC',
drawerItemLabelLevel2: '#F5F5F5',
drawerOpenCommunityBackground: '#191919',
drawerBackground: '#1F1F1F',
subthreadsModalClose: '#808080',
subthreadsModalBackground: '#1F1F1F',
subthreadsModalSearch: '#FFFFFF04',
typeaheadTooltipBackground: '#1F1F1f',
typeaheadTooltipBorder: '#404040',
typeaheadTooltipText: 'white',
messageLabel: '#CCCCCC',
+ modalSeparator: '#404040',
+ secondaryButtonBorder: '#FFFFFF',
});
const colors = { light, dark };
const colorsSelector: (state: AppState) => Colors = createSelector(
(state: AppState) => state.globalThemeInfo.activeTheme,
(theme: ?GlobalTheme) => {
const explicitTheme = theme ? theme : 'light';
return colors[explicitTheme];
},
);
const magicStrings = new Set();
for (const theme in colors) {
for (const magicString in colors[theme]) {
magicStrings.add(magicString);
}
}
type Styles = { [name: string]: { [field: string]: mixed } };
type ReplaceField = (input: any) => any;
export type StyleSheetOf = $ObjMap;
function stylesFromColors(
obj: IS,
themeColors: Colors,
): StyleSheetOf {
const result = {};
for (const key in obj) {
const style = obj[key];
const filledInStyle = { ...style };
for (const styleKey in style) {
const styleValue = style[styleKey];
if (typeof styleValue !== 'string') {
continue;
}
if (magicStrings.has(styleValue)) {
const mapped = themeColors[styleValue];
if (mapped) {
filledInStyle[styleKey] = mapped;
}
}
}
result[key] = filledInStyle;
}
return StyleSheet.create(result);
}
function styleSelector(
obj: IS,
): (state: AppState) => StyleSheetOf {
return createSelector(colorsSelector, (themeColors: Colors) =>
stylesFromColors(obj, themeColors),
);
}
function useStyles(obj: IS): StyleSheetOf {
const ourColors = useColors();
return React.useMemo(
() => stylesFromColors(obj, ourColors),
[obj, ourColors],
);
}
function useOverlayStyles(obj: IS): StyleSheetOf {
const navContext = React.useContext(NavContext);
const navigationState = navContext && navContext.state;
const theme = useSelector(
(state: AppState) => state.globalThemeInfo.activeTheme,
);
const backgroundIsDark = React.useMemo(
() => selectBackgroundIsDark(navigationState, theme),
[navigationState, theme],
);
const syntheticTheme = backgroundIsDark ? 'dark' : 'light';
return React.useMemo(
() => stylesFromColors(obj, colors[syntheticTheme]),
[obj, syntheticTheme],
);
}
function useColors(): Colors {
return useSelector(colorsSelector);
}
function getStylesForTheme(
obj: IS,
theme: GlobalTheme,
): StyleSheetOf {
return stylesFromColors(obj, colors[theme]);
}
export type IndicatorStyle = 'white' | 'black';
function useIndicatorStyle(): IndicatorStyle {
const theme = useSelector(
(state: AppState) => state.globalThemeInfo.activeTheme,
);
return theme && theme === 'dark' ? 'white' : 'black';
}
const indicatorStyleSelector: (state: AppState) => IndicatorStyle =
createSelector(
(state: AppState) => state.globalThemeInfo.activeTheme,
(theme: ?GlobalTheme) => {
return theme && theme === 'dark' ? 'white' : 'black';
},
);
export type KeyboardAppearance = 'default' | 'light' | 'dark';
const keyboardAppearanceSelector: (state: AppState) => KeyboardAppearance =
createSelector(
(state: AppState) => state.globalThemeInfo.activeTheme,
(theme: ?GlobalTheme) => {
return theme && theme === 'dark' ? 'dark' : 'light';
},
);
function useKeyboardAppearance(): KeyboardAppearance {
return useSelector(keyboardAppearanceSelector);
}
export {
colors,
colorsSelector,
styleSelector,
useStyles,
useOverlayStyles,
useColors,
getStylesForTheme,
useIndicatorStyle,
indicatorStyleSelector,
useKeyboardAppearance,
};