diff --git a/native/account/registration/keyserver-selection.react.js b/native/account/registration/keyserver-selection.react.js index ef038048f..984154bde 100644 --- a/native/account/registration/keyserver-selection.react.js +++ b/native/account/registration/keyserver-selection.react.js @@ -1,139 +1,146 @@ // @flow import * as React from 'react'; import { Text, TextInput, View } from 'react-native'; import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import { RegistrationTile, RegistrationTileHeader, } from './registration-tile.react.js'; import CommIcon from '../../components/comm-icon.react.js'; import type { NavigationRoute } from '../../navigation/route-names.js'; import { useStyles, useColors } from '../../themes/colors.js'; type Selection = 'ashoat' | 'custom'; type Props = { +navigation: RegistrationNavigationProp<'KeyserverSelection'>, +route: NavigationRoute<'KeyserverSelection'>, }; // eslint-disable-next-line no-unused-vars function KeyserverSelection(props: Props): React.Node { const [customKeyserver, setCustomKeyserver] = React.useState(''); const customKeyserverTextInputRef = React.useRef(); const [currentSelection, setCurrentSelection] = React.useState(); const selectAshoat = React.useCallback(() => { setCurrentSelection('ashoat'); customKeyserverTextInputRef.current?.blur(); }, []); const customKeyserverEmpty = !customKeyserver; const selectCustom = React.useCallback(() => { setCurrentSelection('custom'); if (customKeyserverEmpty) { customKeyserverTextInputRef.current?.focus(); } }, [customKeyserverEmpty]); const onCustomKeyserverFocus = React.useCallback(() => { setCurrentSelection('custom'); }, []); + let buttonState = 'disabled'; + if ( + currentSelection === 'ashoat' || + (currentSelection === 'custom' && customKeyserver) + ) { + buttonState = 'enabled'; + } const onSubmit = React.useCallback(() => {}, []); const styles = useStyles(unboundStyles); const colors = useColors(); return ( Select a keyserver to join Chat communities on Comm are hosted on keyservers, which are user-operated backends. Keyservers allow Comm to offer strong privacy guarantees without sacrificing functionality. ashoat Ashoat is Comm’s founder, and his keyserver currently hosts most of the communities on Comm. Enter a keyserver - + ); } const unboundStyles = { container: { backgroundColor: 'panelBackground', justifyContent: 'space-between', flex: 1, }, header: { fontSize: 24, color: 'panelForegroundLabel', paddingBottom: 16, }, body: { fontSize: 15, lineHeight: 20, color: 'panelForegroundSecondaryLabel', paddingBottom: 16, }, tileTitleText: { flex: 1, fontSize: 18, color: 'panelForegroundLabel', }, tileBody: { fontSize: 13, color: 'panelForegroundSecondaryLabel', }, cloud: { marginRight: 8, }, keyserverInput: { color: 'panelForegroundLabel', borderColor: 'panelSecondaryForegroundBorder', borderWidth: 1, borderRadius: 4, padding: 12, }, }; export default KeyserverSelection; diff --git a/native/account/registration/registration-button.react.js b/native/account/registration/registration-button.react.js index ec67ca0f9..fe44581a4 100644 --- a/native/account/registration/registration-button.react.js +++ b/native/account/registration/registration-button.react.js @@ -1,37 +1,64 @@ // @flow import * as React from 'react'; import { Text } from 'react-native'; import Button from '../../components/button.react.js'; import { useStyles } from '../../themes/colors.js'; type Props = { +onPress: () => mixed, +label: string, + +state?: 'enabled' | 'disabled' | 'loading', }; function RegistrationButton(props: Props): React.Node { - const { onPress, label } = props; + const { onPress, label, state } = props; + const styles = useStyles(unboundStyles); + const buttonStyle = React.useMemo(() => { + if (state === 'disabled' || state === 'loading') { + return [styles.button, styles.disabledButton]; + } else { + return styles.button; + } + }, [state, styles.button, styles.disabledButton]); + const buttonTextStyle = React.useMemo(() => { + if (state === 'disabled') { + return [styles.buttonText, styles.disabledButtonText]; + } + return styles.buttonText; + }, [state, styles.buttonText, styles.disabledButtonText]); + return ( - ); } const unboundStyles = { button: { backgroundColor: 'purpleButton', borderRadius: 8, margin: 16, }, buttonText: { fontSize: 18, color: 'panelForegroundLabel', textAlign: 'center', padding: 12, }, + disabledButton: { + backgroundColor: 'disabledButton', + }, + disabledButtonText: { + color: 'disabledButtonText', + }, }; export default RegistrationButton; diff --git a/native/themes/colors.js b/native/themes/colors.js index 6f3f867fa..8600c1967 100644 --- a/native/themes/colors.js +++ b/native/themes/colors.js @@ -1,321 +1,323 @@ // @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', + disabledButtonText: '#808080', 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', + disabledButtonText: '#808080', 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, };