Page MenuHomePhabricator

No OneTemporary

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<ChangeThreadSettingsPayload>,
};
-class ColorPickerModal extends React.PureComponent<Props> {
+class ColorSelectorModal extends React.PureComponent<Props> {
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 (
- <Modal modalStyle={[this.props.styles.colorPickerContainer, modalStyle]}>
- <ColorPicker
- defaultColor={color}
- oldColor={threadInfo.color}
+ <Modal
+ modalStyle={[this.props.styles.colorSelectorContainer, modalStyle]}
+ >
+ <ColorSelector
+ currentColor={color}
+ windowWidth={this.props.windowWidth}
onColorSelected={this.onColorSelected}
- style={this.props.styles.colorPicker}
/>
<TouchableHighlight
onPress={this.close}
style={this.props.styles.closeButton}
underlayColor={this.props.colors.modalIosHighlightUnderlay}
>
<Icon
name="close"
size={16}
style={this.props.styles.closeButtonIcon}
/>
</TouchableHighlight>
</Modal>
);
}
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<BaseProps> = React.memo<BaseProps>(
- function ConnectedColorPickerModal(props: BaseProps) {
+const ConnectedColorSelectorModal: React.ComponentType<BaseProps> = React.memo<BaseProps>(
+ 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 (
- <ColorPickerModal
+ <ColorSelectorModal
{...props}
styles={styles}
colors={colors}
windowWidth={windowWidth}
dispatchActionPromise={dispatchActionPromise}
changeThreadSettings={callChangeThreadSettings}
/>
);
},
);
-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<Props> {
render() {
let colorButton;
if (this.props.loadingStatus !== 'loading') {
colorButton = (
<EditSettingButton
onPress={this.onPressEditColor}
canChangeSettings={this.props.canChangeSettings}
style={this.props.styles.colorLine}
/>
);
} else {
colorButton = (
<ActivityIndicator
size="small"
key="activityIndicator"
color={this.props.colors.panelForegroundSecondaryLabel}
/>
);
}
return (
<View style={this.props.styles.colorRow}>
<Text style={[this.props.styles.label, this.props.styles.colorLine]}>
Color
</Text>
<View style={this.props.styles.currentValue}>
<ColorSplotch color={this.props.threadInfo.color} />
</View>
{colorButton}
</View>
);
}
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<BaseProps> = React.memo<BaseProps>(
function ConnectedThreadSettingsColor(props: BaseProps) {
const loadingStatus = useSelector(loadingStatusSelector);
const colors = useColors();
const styles = useStyles(unboundStyles);
return (
<ThreadSettingsColor
{...props}
loadingStatus={loadingStatus}
colors={colors}
styles={styles}
/>
);
},
);
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<typeof View>;
-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<Props, State> {
- 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 = (
- <View style={styles.pickerContainer}>
- <View>
- <View
- style={[styles.triangleContainer, computed.triangleContainer]}
- >
- <View
- style={[
- styles.triangleUnderlayingColor,
- computed.triangleUnderlayingColor,
- ]}
- />
- <Image
- style={computed.triangleImage}
- source={require('../img/hsv_triangle_mask.png')}
- />
- </View>
- <View
- {...pickerResponder.panHandlers}
- style={computed.picker}
- collapsable={false}
- >
- <Image
- source={require('../img/color-circle.png')}
- resizeMode="contain"
- style={styles.pickerImage}
- />
- <View style={[styles.pickerIndicator, computed.pickerIndicator]}>
- <View
- style={[
- styles.pickerIndicatorTick,
- computed.pickerIndicatorTick,
- ]}
- />
- </View>
- <View style={[styles.svIndicator, computed.svIndicator]} />
- </View>
- </View>
- </View>
- );
- }
-
- let oldColorButton = null;
- if (this.props.oldColor) {
- const oldTinyColor = tinycolor(this.props.oldColor);
- const oldButtonTextStyle = {
- color: oldTinyColor.isDark() ? 'white' : 'black',
- };
- oldColorButton = (
- <Button
- topStyle={styles.colorPreview}
- style={[
- styles.buttonContents,
- { backgroundColor: oldTinyColor.toHexString() },
- ]}
- onPress={this._onOldColorSelected}
- iosFormat="highlight"
- iosHighlightUnderlayColor={underlayColor}
- iosActiveOpacity={0.6}
- >
- <Text style={[styles.buttonText, oldButtonTextStyle]}>
- {this.props.oldButtonText}
- </Text>
- </Button>
- );
- }
- 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 (
- <View
- onLayout={this._onLayout}
- ref={this.pickerContainerRef}
- style={style}
- >
- {picker}
- <View style={[styles.colorPreviews, colorPreviewsStyle]}>
- {oldColorButton}
- <Button
- style={[styles.buttonContents, buttonContentsStyle]}
- topStyle={styles.colorPreview}
- onPress={this._onColorSelected}
- iosFormat="highlight"
- iosHighlightUnderlayColor={underlayColor}
- iosActiveOpacity={0.6}
- >
- <Text style={[styles.buttonText, buttonTextStyle]}>
- {this.props.buttonText}
- </Text>
- </Button>
- </View>
- </View>
- );
- }
-
- 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<BaseProps> = React.memo<BaseProps>(
- function ConnectedColorPicker(props: BaseProps) {
- const colors = useColors();
- return <ColorPicker {...props} colors={colors} />;
- },
-);
-
-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<RootRouterNavigationProp<>>;
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 (
<RootNavigatorContext.Provider value={rootNavigationContext}>
<StackView
{...rest}
state={state}
descriptors={descriptors}
navigation={navigation}
keyboardHandlingEnabled={keyboardHandlingEnabled}
detachInactiveScreens={Platform.OS !== 'ios'}
/>
</RootNavigatorContext.Provider>
);
}
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<ScreenParamList> = $Keys<ScreenParamList>,
> = RootRouterNavigationProp<ScreenParamList, RouteName>;
const Root = createRootNavigator<
ScreenParamList,
RootParamList,
RootNavigationProp<>,
>();
function RootComponent(): React.Node {
return (
<Root.Navigator
mode="modal"
headerMode="none"
screenOptions={defaultScreenOptions}
>
<Root.Screen
name={LoggedOutModalRouteName}
component={LoggedOutModal}
options={disableGesturesScreenOptions}
/>
<Root.Screen name={AppRouteName} component={AppNavigator} />
<Root.Screen
name={ThreadPickerModalRouteName}
component={ThreadPickerModal}
options={modalOverlayScreenOptions}
/>
<Root.Screen
name={ImagePasteModalRouteName}
component={ImagePasteModal}
options={modalOverlayScreenOptions}
/>
<Root.Screen
name={AddUsersModalRouteName}
component={AddUsersModal}
options={modalOverlayScreenOptions}
/>
<Root.Screen
name={CustomServerModalRouteName}
component={CustomServerModal}
options={modalOverlayScreenOptions}
/>
<Root.Screen
- name={ColorPickerModalRouteName}
- component={ColorPickerModal}
+ name={ColorSelectorModalRouteName}
+ component={ColorSelectorModal}
options={modalOverlayScreenOptions}
/>
<Root.Screen
name={ComposeSubchannelModalRouteName}
component={ComposeSubchannelModal}
options={modalOverlayScreenOptions}
/>
<Root.Screen
name={SidebarListModalRouteName}
component={SidebarListModal}
options={modalOverlayScreenOptions}
/>
</Root.Navigator>
);
}
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<RouteName: string = $Keys<ScreenParamList>> = {
...LeafRoute<RouteName>,
+params: $ElementType<ScreenParamList, RouteName>,
};
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,
];

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 25, 7:25 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2700886
Default Alt Text
(42 KB)

Event Timeline