diff --git a/native/chat/settings/color-picker-modal.react.js b/native/chat/settings/color-selector-modal.react.js similarity index 82% rename from native/chat/settings/color-picker-modal.react.js rename to native/chat/settings/color-selector-modal.react.js index d772b2d85..e87274065 100644 --- a/native/chat/settings/color-picker-modal.react.js +++ b/native/chat/settings/color-selector-modal.react.js @@ -1,175 +1,176 @@ // @flow import * as React from 'react'; import { TouchableHighlight, Alert } from 'react-native'; import Icon from 'react-native-vector-icons/FontAwesome'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from 'lib/actions/thread-actions'; import { type ThreadInfo, type ChangeThreadSettingsPayload, type UpdateThreadRequest, } from 'lib/types/thread-types'; import type { DispatchActionPromise } from 'lib/utils/action-utils'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils'; -import ColorPicker from '../../components/color-picker.react'; +import ColorSelector from '../../components/color-selector.react'; import Modal from '../../components/modal.react'; import type { RootNavigationProp } from '../../navigation/root-navigator.react'; import type { NavigationRoute } from '../../navigation/route-names'; import { useSelector } from '../../redux/redux-utils'; import { type Colors, useStyles, useColors } from '../../themes/colors'; -export type ColorPickerModalParams = { +export type ColorSelectorModalParams = { presentedFrom: string, color: string, threadInfo: ThreadInfo, setColor: (color: string) => void, }; type BaseProps = { - +navigation: RootNavigationProp<'ColorPickerModal'>, - +route: NavigationRoute<'ColorPickerModal'>, + +navigation: RootNavigationProp<'ColorSelectorModal'>, + +route: NavigationRoute<'ColorSelectorModal'>, }; type Props = { ...BaseProps, // Redux state +colors: Colors, +styles: typeof unboundStyles, +windowWidth: number, // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs +changeThreadSettings: ( request: UpdateThreadRequest, ) => Promise, }; -class ColorPickerModal extends React.PureComponent { +class ColorSelectorModal extends React.PureComponent { render() { - const { color, threadInfo } = this.props.route.params; + const { color } = this.props.route.params; // Based on the assumption we are always in portrait, // and consequently width is the lowest dimensions - const modalStyle = { height: this.props.windowWidth - 5 }; + const modalStyle = { height: 0.75 * this.props.windowWidth }; return ( - - + ); } close = () => { this.props.navigation.goBackOnce(); }; onColorSelected = (color: string) => { const colorEditValue = color.substr(1); this.props.route.params.setColor(colorEditValue); this.close(); this.props.dispatchActionPromise( changeThreadSettingsActionTypes, this.editColor(colorEditValue), { customKeyName: `${changeThreadSettingsActionTypes.started}:color` }, ); }; async editColor(newColor: string) { const threadID = this.props.route.params.threadInfo.id; try { return await this.props.changeThreadSettings({ threadID, changes: { color: newColor }, }); } catch (e) { Alert.alert( 'Unknown error', 'Uhh... try again?', [{ text: 'OK', onPress: this.onErrorAcknowledged }], { cancelable: false }, ); throw e; } } onErrorAcknowledged = () => { const { threadInfo, setColor } = this.props.route.params; setColor(threadInfo.color); }; } const unboundStyles = { closeButton: { borderRadius: 3, height: 18, position: 'absolute', right: 5, top: 5, width: 18, }, closeButtonIcon: { color: 'modalBackgroundSecondaryLabel', left: 3, position: 'absolute', }, - colorPicker: { + colorSelector: { bottom: 10, left: 10, position: 'absolute', right: 10, top: 10, }, - colorPickerContainer: { + colorSelectorContainer: { backgroundColor: 'modalBackground', borderColor: 'modalForegroundBorder', borderRadius: 5, borderWidth: 2, flex: 0, marginHorizontal: 15, marginVertical: 20, }, }; -const ConnectedColorPickerModal: React.ComponentType = React.memo( - function ConnectedColorPickerModal(props: BaseProps) { +const ConnectedColorSelectorModal: React.ComponentType = React.memo( + function ConnectedColorSelectorModal(props: BaseProps) { const styles = useStyles(unboundStyles); const colors = useColors(); const windowWidth = useSelector(state => state.dimensions.width); const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadSettings = useServerCall(changeThreadSettings); return ( - ); }, ); -export default ConnectedColorPickerModal; +export default ConnectedColorSelectorModal; diff --git a/native/chat/settings/thread-settings-color.react.js b/native/chat/settings/thread-settings-color.react.js index 5da8c394c..cc2d38f00 100644 --- a/native/chat/settings/thread-settings-color.react.js +++ b/native/chat/settings/thread-settings-color.react.js @@ -1,123 +1,123 @@ // @flow import * as React from 'react'; import { Text, ActivityIndicator, View, Platform } from 'react-native'; import { changeThreadSettingsActionTypes } from 'lib/actions/thread-actions'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import type { LoadingStatus } from 'lib/types/loading-types'; import { type ThreadInfo } from 'lib/types/thread-types'; import ColorSplotch from '../../components/color-splotch.react'; import EditSettingButton from '../../components/edit-setting-button.react'; -import { ColorPickerModalRouteName } from '../../navigation/route-names'; +import { ColorSelectorModalRouteName } from '../../navigation/route-names'; import { useSelector } from '../../redux/redux-utils'; import { type Colors, useColors, useStyles } from '../../themes/colors'; import type { ThreadSettingsNavigate } from './thread-settings.react'; type BaseProps = { +threadInfo: ThreadInfo, +colorEditValue: string, +setColorEditValue: (color: string) => void, +canChangeSettings: boolean, +navigate: ThreadSettingsNavigate, +threadSettingsRouteKey: string, }; type Props = { ...BaseProps, // Redux state +loadingStatus: LoadingStatus, +colors: Colors, +styles: typeof unboundStyles, }; class ThreadSettingsColor extends React.PureComponent { render() { let colorButton; if (this.props.loadingStatus !== 'loading') { colorButton = ( ); } else { colorButton = ( ); } return ( Color {colorButton} ); } onPressEditColor = () => { this.props.navigate({ - name: ColorPickerModalRouteName, + name: ColorSelectorModalRouteName, params: { presentedFrom: this.props.threadSettingsRouteKey, color: this.props.colorEditValue, threadInfo: this.props.threadInfo, setColor: this.props.setColorEditValue, }, }); }; } const unboundStyles = { colorLine: { lineHeight: Platform.select({ android: 22, default: 25 }), }, colorRow: { backgroundColor: 'panelForeground', flexDirection: 'row', paddingBottom: 8, paddingHorizontal: 24, paddingTop: 4, }, currentValue: { flex: 1, paddingLeft: 4, }, label: { color: 'panelForegroundTertiaryLabel', fontSize: 16, width: 96, }, }; const loadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:color`, ); const ConnectedThreadSettingsColor: React.ComponentType = React.memo( function ConnectedThreadSettingsColor(props: BaseProps) { const loadingStatus = useSelector(loadingStatusSelector); const colors = useColors(); const styles = useStyles(unboundStyles); return ( ); }, ); export default ConnectedThreadSettingsColor; diff --git a/native/components/color-picker.react.js b/native/components/color-picker.react.js deleted file mode 100644 index 878cbd43f..000000000 --- a/native/components/color-picker.react.js +++ /dev/null @@ -1,646 +0,0 @@ -// @flow - -import invariant from 'invariant'; -import * as React from 'react'; -import { - View, - Image, - StyleSheet, - I18nManager, - PanResponder, - Text, - Keyboard, -} from 'react-native'; -import tinycolor from 'tinycolor2'; - -import { type Colors, useColors } from '../themes/colors'; -import type { LayoutEvent } from '../types/react-native'; -import type { ViewStyle } from '../types/styles'; -import Button from './button.react'; - -type PanEvent = $ReadOnly<{ - nativeEvent: $ReadOnly<{ - pageX: number, - pageY: number, - ... - }>, - ... -}>; -type HSVColor = { h: number, s: number, v: number }; -type PickerContainer = React.ElementRef; -type BaseProps = { - +color?: string | HSVColor, - +defaultColor?: string, - +oldColor?: ?string, - +onColorChange?: (color: HSVColor) => void, - +onColorSelected?: (color: string) => void, - +onOldColorSelected?: (color: string) => void, - +style?: ViewStyle, - +buttonText?: string, - +oldButtonText?: string, -}; -type Props = { - ...BaseProps, - +colors: Colors, -}; -type State = { - +color: HSVColor, - +pickerSize: ?number, -}; -class ColorPicker extends React.PureComponent { - static defaultProps = { - buttonText: 'Select', - oldButtonText: 'Reset', - }; - _layout = { width: 0, height: 0 }; - _pageX = 0; - _pageY = 0; - _pickerContainer: ?PickerContainer = null; - _pickerResponder = null; - _changingHColor = false; - - constructor(props: Props) { - super(props); - let color; - if (props.defaultColor) { - color = tinycolor(props.defaultColor).toHsv(); - } else if (props.oldColor) { - color = tinycolor(props.oldColor).toHsv(); - } else { - color = { h: 0, s: 1, v: 1 }; - } - this.state = { color, pickerSize: null }; - const handleColorChange = ({ x, y }: { x: number, y: number }) => { - if (this._changingHColor) { - this._handleHColorChange({ x, y }); - } else { - this._handleSVColorChange({ x, y }); - } - }; - this._pickerResponder = PanResponder.create({ - onStartShouldSetPanResponder: () => true, - onStartShouldSetPanResponderCapture: () => true, - onMoveShouldSetPanResponder: () => true, - onMoveShouldSetPanResponderCapture: () => true, - onPanResponderTerminationRequest: () => true, - onPanResponderGrant: (evt: PanEvent) => { - const x = evt.nativeEvent.pageX; - const y = evt.nativeEvent.pageY; - const { s, v } = this._computeColorFromTriangle({ x, y }); - this._changingHColor = s > 1 || s < 0 || v > 1 || v < 0; - handleColorChange({ x, y }); - }, - onPanResponderMove: (evt: PanEvent) => - handleColorChange({ - x: evt.nativeEvent.pageX, - y: evt.nativeEvent.pageY, - }), - onPanResponderRelease: () => true, - }); - } - - componentDidMount() { - Keyboard.dismiss(); - } - - _getColor(): HSVColor { - const passedColor = - typeof this.props.color === 'string' - ? tinycolor(this.props.color).toHsv() - : this.props.color; - return passedColor || this.state.color; - } - - _onColorSelected = () => { - const { onColorSelected } = this.props; - const color = tinycolor(this._getColor()).toHexString(); - onColorSelected && onColorSelected(color); - }; - - _onOldColorSelected = () => { - const { oldColor, onOldColorSelected } = this.props; - const color = tinycolor(oldColor); - this.setState({ color: color.toHsv() }); - onOldColorSelected && onOldColorSelected(color.toHexString()); - }; - - _onSValueChange = (s: number) => { - const { h, v } = this._getColor(); - this._onColorChange({ h, s, v }); - }; - - _onVValueChange = (v: number) => { - const { h, s } = this._getColor(); - this._onColorChange({ h, s, v }); - }; - - _onColorChange(color: HSVColor) { - this.setState({ color }); - if (this.props.onColorChange) { - this.props.onColorChange(color); - } - } - - _onLayout = (l: LayoutEvent) => { - this._layout = l.nativeEvent.layout; - const { width, height } = this._layout; - const pickerSize = Math.round(Math.min(width, height)); - if ( - !this.state.pickerSize || - Math.abs(this.state.pickerSize - pickerSize) >= 3 - ) { - this.setState({ pickerSize }); - } - - // We need to get pageX/pageY, ie. the absolute position of the picker on - // the screen. This is because PanResponder's relative position information - // is double broken (#12591, #15290). Unfortunately, the only way to get - // absolute positioning for a View is via measure() after onLayout (#10556). - // The setTimeout is necessary to make sure that the ColorPickerModal - // completes its slide-in animation before we measure. - setTimeout(() => { - if (!this._pickerContainer) { - return; - } - this._pickerContainer.measure((x, y, cWidth, cHeight, pageX, pageY) => { - const { pickerPadding } = getPickerProperties(pickerSize); - this._pageX = pageX; - this._pageY = pageY - pickerPadding - 3; - }); - }, 500); - }; - - _computeHValue(x: number, y: number) { - const pickerSize = this.state.pickerSize; - invariant( - pickerSize !== null && pickerSize !== undefined, - 'pickerSize should be set', - ); - const dx = x - pickerSize / 2; - const dy = y - pickerSize / 2; - const rad = Math.atan2(dx, dy) + Math.PI + Math.PI / 2; - return ((rad * 180) / Math.PI) % 360; - } - - _hValueToRad(deg: number) { - const rad = (deg * Math.PI) / 180; - return rad - Math.PI - Math.PI / 2; - } - - getColor(): string { - return tinycolor(this._getColor()).toHexString(); - } - - _handleHColorChange({ x, y }: { x: number, y: number }) { - const { s, v } = this._getColor(); - const { pickerSize } = this.state; - invariant( - pickerSize !== null && pickerSize !== undefined, - 'pickerSize should be set', - ); - const marginLeft = (this._layout.width - pickerSize) / 2; - const marginTop = (this._layout.height - pickerSize) / 2; - const relativeX = x - this._pageX - marginLeft; - const relativeY = y - this._pageY - marginTop; - const h = this._computeHValue(relativeX, relativeY); - this._onColorChange({ h, s, v }); - } - - _handleSVColorChange({ x, y }: { x: number, y: number }) { - const { h, s: rawS, v: rawV } = this._computeColorFromTriangle({ x, y }); - const s = Math.min(Math.max(0, rawS), 1); - const v = Math.min(Math.max(0, rawV), 1); - this._onColorChange({ h, s, v }); - } - - _normalizeTriangleTouch( - s: number, - v: number, - sRatio: number, - ): { s: number, v: number } { - // relative size to be considered as corner zone - const CORNER_ZONE_SIZE = 0.12; - // relative triangle margin to be considered as touch in triangle - const NORMAL_MARGIN = 0.1; - // relative triangle margin to be considered as touch in triangle - // in corner zone - const CORNER_MARGIN = 0.05; - let margin = NORMAL_MARGIN; - - const posNS = v > 0 ? 1 - (1 - s) * sRatio : 1 - s * sRatio; - const negNS = v > 0 ? s * sRatio : (1 - s) * sRatio; - // normalized s value according to ratio and s value - const ns = s > 1 ? posNS : negNS; - - const rightCorner = s > 1 - CORNER_ZONE_SIZE && v > 1 - CORNER_ZONE_SIZE; - const leftCorner = ns < 0 + CORNER_ZONE_SIZE && v > 1 - CORNER_ZONE_SIZE; - const topCorner = ns < 0 + CORNER_ZONE_SIZE && v < 0 + CORNER_ZONE_SIZE; - if (rightCorner) { - return { s, v }; - } - if (leftCorner || topCorner) { - margin = CORNER_MARGIN; - } - // color normalization according to margin - s = s < 0 && ns > 0 - margin ? 0 : s; - s = s > 1 && ns < 1 + margin ? 1 : s; - v = v < 0 && v > 0 - margin ? 0 : v; - v = v > 1 && v < 1 + margin ? 1 : v; - return { s, v }; - } - - /** - * Computes s, v from position (x, y). If position is outside of triangle, - * it will return invalid values (greater than 1 or lower than 0) - */ - _computeColorFromTriangle({ x, y }: { x: number, y: number }): HSVColor { - const { pickerSize } = this.state; - invariant( - pickerSize !== null && pickerSize !== undefined, - 'pickerSize should be set', - ); - const { triangleHeight, triangleWidth } = getPickerProperties(pickerSize); - - const left = pickerSize / 2 - triangleWidth / 2; - const top = pickerSize / 2 - (2 * triangleHeight) / 3; - - // triangle relative coordinates - const marginLeft = (this._layout.width - pickerSize) / 2; - const marginTop = (this._layout.height - pickerSize) / 2; - const relativeX = x - this._pageX - marginLeft - left; - const relativeY = y - this._pageY - marginTop - top; - - // rotation - const { h } = this._getColor(); - // starting angle is 330 due to comfortable calculation - const deg = (h - 330 + 360) % 360; - const rad = (deg * Math.PI) / 180; - const center = { - x: triangleWidth / 2, - y: (2 * triangleHeight) / 3, - }; - const rotated = rotatePoint({ x: relativeX, y: relativeY }, rad, center); - - const line = (triangleWidth * rotated.y) / triangleHeight; - const margin = - triangleWidth / 2 - ((triangleWidth / 2) * rotated.y) / triangleHeight; - const s = (rotated.x - margin) / line; - const v = rotated.y / triangleHeight; - - // normalize - const normalized = this._normalizeTriangleTouch( - s, - v, - line / triangleHeight, - ); - - return { h, s: normalized.s, v: normalized.v }; - } - - render() { - const { pickerSize } = this.state; - const { style } = this.props; - const color = this._getColor(); - const tc = tinycolor(color); - const selectedColor: string = tc.toHexString(); - const isDark: boolean = tc.isDark(); - const { modalIosHighlightUnderlay: underlayColor } = this.props.colors; - - let picker = null; - if (pickerSize) { - const pickerResponder = this._pickerResponder; - invariant(pickerResponder, 'should be set'); - const { h } = color; - const indicatorColor = tinycolor({ h, s: 1, v: 1 }).toHexString(); - const angle = this._hValueToRad(h); - const computed = makeComputedStyles({ - pickerSize, - selectedColor, - selectedColorHsv: color, - indicatorColor, - oldColor: this.props.oldColor, - angle, - isRTL: I18nManager.isRTL, - }); - picker = ( - - - - - - - - - - - - - - - - ); - } - - let oldColorButton = null; - if (this.props.oldColor) { - const oldTinyColor = tinycolor(this.props.oldColor); - const oldButtonTextStyle = { - color: oldTinyColor.isDark() ? 'white' : 'black', - }; - oldColorButton = ( - - ); - } - const colorPreviewsStyle = { - height: this.state.pickerSize - ? this.state.pickerSize * 0.1 // responsive height - : 20, - }; - const buttonContentsStyle = { - backgroundColor: selectedColor, - }; - const buttonTextStyle = { - color: isDark ? 'white' : 'black', - }; - return ( - - {picker} - - {oldColorButton} - - - - ); - } - - pickerContainerRef = (pickerContainer: ?PickerContainer) => { - this._pickerContainer = pickerContainer; - }; -} - -function getPickerProperties(pickerSize) { - const indicatorPickerRatio = 42 / 510; // computed from picker image - const originalIndicatorSize = indicatorPickerRatio * pickerSize; - const indicatorSize = originalIndicatorSize; - const pickerPadding = originalIndicatorSize / 3; - - const triangleSize = pickerSize - 6 * pickerPadding; - const triangleRadius = triangleSize / 2; - const triangleHeight = (triangleRadius * 3) / 2; - // pythagorean theorem - const triangleWidth = 2 * triangleRadius * Math.sqrt(3 / 4); - - return { - triangleSize, - triangleRadius, - triangleHeight, - triangleWidth, - indicatorPickerRatio, - indicatorSize, - pickerPadding, - }; -} - -const makeComputedStyles = ({ - indicatorColor, - angle, - pickerSize, - selectedColorHsv, - isRTL, -}) => { - const { - triangleSize, - triangleHeight, - triangleWidth, - indicatorSize, - pickerPadding, - } = getPickerProperties(pickerSize); - - /* ===== INDICATOR ===== */ - const indicatorRadius = pickerSize / 2 - indicatorSize / 2 - pickerPadding; - const mx = pickerSize / 2; - const my = pickerSize / 2; - const dx = Math.cos(angle) * indicatorRadius; - const dy = Math.sin(angle) * indicatorRadius; - - /* ===== TRIANGLE ===== */ - const triangleTop = pickerPadding * 3; - const triangleLeft = pickerPadding * 3; - const triangleAngle = -angle + Math.PI / 3; - - /* ===== SV INDICATOR ===== */ - const markerColor = 'rgba(0,0,0,0.8)'; - const { s, v, h } = selectedColorHsv; - const svIndicatorSize = 18; - const svY = v * triangleHeight; - const margin = triangleWidth / 2 - v * (triangleWidth / 2); - const svX = s * (triangleWidth - 2 * margin) + margin; - const svIndicatorMarginLeft = (pickerSize - triangleWidth) / 2; - const svIndicatorMarginTop = (pickerSize - (4 * triangleHeight) / 3) / 2; - - // starting angle is 330 due to comfortable calculation - const deg = (h - 330 + 360) % 360; - const rad = (deg * Math.PI) / 180; - const center = { x: pickerSize / 2, y: pickerSize / 2 }; - const notRotatedPoint = { - x: svIndicatorMarginTop + svY, - y: svIndicatorMarginLeft + svX, - }; - const svIndicatorPoint = rotatePoint(notRotatedPoint, rad, center); - - const offsetDirection: string = isRTL ? 'right' : 'left'; - return { - picker: { - padding: pickerPadding, - width: pickerSize, - height: pickerSize, - }, - pickerIndicator: { - top: mx + dx - indicatorSize / 2, - [offsetDirection]: my + dy - indicatorSize / 2, - width: indicatorSize, - height: indicatorSize, - transform: [ - { - rotate: -angle + 'rad', - }, - ], - }, - pickerIndicatorTick: { - height: indicatorSize / 2, - backgroundColor: markerColor, - }, - svIndicator: { - top: svIndicatorPoint.x - svIndicatorSize / 2, - [offsetDirection]: svIndicatorPoint.y - svIndicatorSize / 2, - width: svIndicatorSize, - height: svIndicatorSize, - borderRadius: svIndicatorSize / 2, - borderColor: markerColor, - }, - triangleContainer: { - width: triangleSize, - height: triangleSize, - transform: [ - { - rotate: triangleAngle + 'rad', - }, - ], - top: triangleTop, - left: triangleLeft, - }, - triangleImage: { - width: triangleWidth, - height: triangleHeight, - }, - triangleUnderlayingColor: { - left: (triangleSize - triangleWidth) / 2, - borderLeftWidth: triangleWidth / 2, - borderRightWidth: triangleWidth / 2, - borderBottomWidth: triangleHeight, - borderBottomColor: indicatorColor, - }, - }; -}; - -type Point = { x: number, y: number }; -function rotatePoint( - point: Point, - angle: number, - center: Point = { x: 0, y: 0 }, -) { - // translation to origin - const transOriginX = point.x - center.x; - const transOriginY = point.y - center.y; - - // rotation around origin - const rotatedX = - transOriginX * Math.cos(angle) - transOriginY * Math.sin(angle); - const rotatedY = - transOriginY * Math.cos(angle) + transOriginX * Math.sin(angle); - - // translate back from origin - const normalizedX = rotatedX + center.x; - const normalizedY = rotatedY + center.y; - return { - x: normalizedX, - y: normalizedY, - }; -} - -const styles = StyleSheet.create({ - buttonContents: { - borderRadius: 3, - flex: 1, - padding: 3, - }, - buttonText: { - flex: 1, - fontSize: 20, - textAlign: 'center', - }, - colorPreview: { - flex: 1, - marginHorizontal: 5, - }, - colorPreviews: { - flexDirection: 'row', - }, - pickerContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - }, - pickerImage: { - flex: 1, - height: null, - width: null, - }, - pickerIndicator: { - alignItems: 'center', - justifyContent: 'center', - position: 'absolute', - }, - pickerIndicatorTick: { - width: 5, - }, - svIndicator: { - borderWidth: 4, - position: 'absolute', - }, - triangleContainer: { - alignItems: 'center', - position: 'absolute', - }, - triangleUnderlayingColor: { - backgroundColor: 'transparent', - borderLeftColor: 'transparent', - borderRightColor: 'transparent', - borderStyle: 'solid', - height: 0, - position: 'absolute', - top: 0, - width: 0, - }, -}); - -const ConnectedColorPicker: React.ComponentType = React.memo( - function ConnectedColorPicker(props: BaseProps) { - const colors = useColors(); - return ; - }, -); - -export default ConnectedColorPicker; diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js index 04d41e805..156a24644 100644 --- a/native/navigation/root-navigator.react.js +++ b/native/navigation/root-navigator.react.js @@ -1,184 +1,184 @@ // @flow import { createNavigatorFactory, useNavigationBuilder, type StackNavigationState, type StackOptions, type StackNavigationEventMap, type StackNavigatorProps, type ExtraStackNavigatorProps, } 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 LoggedOutModal from '../account/logged-out-modal.react'; import ThreadPickerModal from '../calendar/thread-picker-modal.react'; import ImagePasteModal from '../chat/image-paste-modal.react'; import AddUsersModal from '../chat/settings/add-users-modal.react'; -import ColorPickerModal from '../chat/settings/color-picker-modal.react'; +import ColorSelectorModal from '../chat/settings/color-selector-modal.react'; import ComposeSubchannelModal from '../chat/settings/compose-subchannel-modal.react'; import SidebarListModal from '../chat/sidebar-list-modal.react'; import CustomServerModal from '../profile/custom-server-modal.react'; import AppNavigator from './app-navigator.react'; import { defaultStackScreenOptions } from './options'; import { RootNavigatorContext } from './root-navigator-context'; import RootRouter, { type RootRouterNavigationProp } from './root-router'; import { LoggedOutModalRouteName, AppRouteName, ThreadPickerModalRouteName, ImagePasteModalRouteName, AddUsersModalRouteName, CustomServerModalRouteName, - ColorPickerModalRouteName, + ColorSelectorModalRouteName, ComposeSubchannelModalRouteName, SidebarListModalRouteName, type ScreenParamList, type RootParamList, } from './route-names'; enableScreens(); type RootNavigatorProps = StackNavigatorProps>; function RootNavigator({ initialRouteName, children, screenOptions, ...rest }: RootNavigatorProps) { const { state, descriptors, navigation } = useNavigationBuilder(RootRouter, { initialRouteName, children, screenOptions, }); const [keyboardHandlingEnabled, setKeyboardHandlingEnabled] = React.useState( true, ); const rootNavigationContext = React.useMemo( () => ({ setKeyboardHandlingEnabled }), [setKeyboardHandlingEnabled], ); return ( ); } const createRootNavigator = createNavigatorFactory< StackNavigationState, StackOptions, StackNavigationEventMap, RootRouterNavigationProp<>, 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, cardStyle: { backgroundColor: 'transparent' }, ...transitionPreset, }; const disableGesturesScreenOptions = { gestureEnabled: false, }; const modalOverlayScreenOptions = { cardOverlayEnabled: true, }; export type RootNavigationProp< RouteName: $Keys = $Keys, > = RootRouterNavigationProp; const Root = createRootNavigator< ScreenParamList, RootParamList, RootNavigationProp<>, >(); function RootComponent(): React.Node { return ( ); } export default RootComponent; diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js index a9413c1b7..90b2ad994 100644 --- a/native/navigation/route-names.js +++ b/native/navigation/route-names.js @@ -1,175 +1,175 @@ // @flow import type { LeafRoute } from '@react-navigation/native'; import type { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react'; import type { ComposeSubchannelParams } from '../chat/compose-subchannel.react'; import type { ImagePasteModalParams } from '../chat/image-paste-modal.react'; import type { MessageListParams } from '../chat/message-list-types'; import type { MultimediaMessageTooltipModalParams } from '../chat/multimedia-message-tooltip-modal.react'; import type { RobotextMessageTooltipModalParams } from '../chat/robotext-message-tooltip-modal.react'; import type { AddUsersModalParams } from '../chat/settings/add-users-modal.react'; -import type { ColorPickerModalParams } from '../chat/settings/color-picker-modal.react'; +import type { ColorSelectorModalParams } from '../chat/settings/color-selector-modal.react'; import type { ComposeSubchannelModalParams } from '../chat/settings/compose-subchannel-modal.react'; import type { DeleteThreadParams } from '../chat/settings/delete-thread.react'; import type { ThreadSettingsMemberTooltipModalParams } from '../chat/settings/thread-settings-member-tooltip-modal.react'; import type { ThreadSettingsParams } from '../chat/settings/thread-settings.react'; import type { SidebarListModalParams } from '../chat/sidebar-list-modal.react'; import type { TextMessageTooltipModalParams } from '../chat/text-message-tooltip-modal.react'; import type { CameraModalParams } from '../media/camera-modal.react'; import type { ImageModalParams } from '../media/image-modal.react'; import type { VideoPlaybackModalParams } from '../media/video-playback-modal.react'; import type { CustomServerModalParams } from '../profile/custom-server-modal.react'; import type { RelationshipListItemTooltipModalParams } from '../profile/relationship-list-item-tooltip-modal.react'; import type { ActionResultModalParams } from './action-result-modal.react'; 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 CameraModalRouteName = 'CameraModal'; export const ChatRouteName = 'Chat'; export const ChatThreadListRouteName = 'ChatThreadList'; -export const ColorPickerModalRouteName = 'ColorPickerModal'; +export const ColorSelectorModalRouteName = 'ColorSelectorModal'; export const ComposeSubchannelModalRouteName = 'ComposeSubchannelModal'; export const ComposeSubchannelRouteName = 'ComposeSubchannel'; 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 FriendListRouteName = 'FriendList'; export const HomeChatThreadListRouteName = 'HomeChatThreadList'; export const ImageModalRouteName = 'ImageModal'; export const ImagePasteModalRouteName = 'ImagePasteModal'; export const LoggedOutModalRouteName = 'LoggedOutModal'; export const MessageListRouteName = 'MessageList'; 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 TabNavigatorRouteName = 'TabNavigator'; export const TextMessageTooltipModalRouteName = 'TextMessageTooltipModal'; export const ThreadPickerModalRouteName = 'ThreadPickerModal'; export const ThreadSettingsMemberTooltipModalRouteName = 'ThreadSettingsMemberTooltipModal'; export const ThreadSettingsRouteName = 'ThreadSettings'; export const VideoPlaybackModalRouteName = 'VideoPlaybackModal'; export type RootParamList = { +LoggedOutModal: void, +App: void, +ThreadPickerModal: ThreadPickerModalParams, +AddUsersModal: AddUsersModalParams, +CustomServerModal: CustomServerModalParams, - +ColorPickerModal: ColorPickerModalParams, + +ColorSelectorModal: ColorSelectorModalParams, +ComposeSubchannelModal: ComposeSubchannelModalParams, +SidebarListModal: SidebarListModalParams, +ImagePasteModal: ImagePasteModalParams, }; 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 = { +TabNavigator: void, +ImageModal: ImageModalParams, +ActionResultModal: ActionResultModalParams, +CameraModal: CameraModalParams, +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, +DeleteThread: DeleteThreadParams, }; export type ChatTopTabsParamList = { +HomeChatThreadList: void, +BackgroundChatThreadList: void, }; export type ProfileParamList = { +ProfileScreen: void, +EditPassword: void, +DeleteAccount: void, +BuildInfo: void, +DevTools: void, +AppearancePreferences: void, +PrivacyPreferences: void, +DefaultNotifications: void, +FriendList: void, +BlockList: void, }; export type ScreenParamList = { ...RootParamList, ...OverlayParamList, ...TabParamList, ...ChatParamList, ...ChatTopTabsParamList, ...ProfileParamList, }; export type NavigationRoute> = { ...LeafRoute, +params: $ElementType, }; export const accountModals = [LoggedOutModalRouteName]; export const scrollBlockingModals = [ ImageModalRouteName, MultimediaMessageTooltipModalRouteName, TextMessageTooltipModalRouteName, ThreadSettingsMemberTooltipModalRouteName, RelationshipListItemTooltipModalRouteName, RobotextMessageTooltipModalRouteName, VideoPlaybackModalRouteName, ]; export const chatRootModals = [ AddUsersModalRouteName, - ColorPickerModalRouteName, + ColorSelectorModalRouteName, ComposeSubchannelModalRouteName, ]; export const threadRoutes = [ MessageListRouteName, ThreadSettingsRouteName, DeleteThreadRouteName, ComposeSubchannelRouteName, ];