Sandwich IoT
Published © GPL3+

Develop RN Panels for Smart Lights

With a smart light app, users can take full control of smart lights in their homes. Change the brightness and colors of lights and personali

BeginnerFull instructions provided12 hours177
Develop RN Panels for Smart Lights

Things used in this project

Hardware components

White and colored light (optional)
Any Powered by Tuya lights are applicable. Find more in our TuyaGo.
×1

Software apps and online services

Panel SDK
https://github.com/tuya/tuya-panel-sdk/
Panel kit
https://github.com/tuya/tuya-panel-kit
Panel template
https://github.com/tuya/tuya-panel-demo/tree/master/examples/lampClassic
Command line interface
https://github.com/tuya/tuya-panel-cli/

Story

Read more

Code

Code snippet #7

Plain text
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ api // Place a series of cloud APIs used in the project.
β”‚   β”œβ”€β”€ components // Place reusable functional components used in the project.
β”‚   β”œβ”€β”€ composeLayout.tsx // Encapsulate device events and device information required in the panel.
β”‚   β”œβ”€β”€ config // Place frequently-used configuration files.
β”‚   β”œβ”€β”€ containers // Place page-level components of the project.
β”‚   β”œβ”€β”€ i18n // Place configuration files for multiple languages.
β”‚   β”œβ”€β”€ main.tsx // The project entry files that are inherited from NavigatorLayout. Rewrite the hookRoute method to define necessary configurations, such as the background and top bar. Rewrite the renderScene method to control routing jump.
β”‚   β”œβ”€β”€ redux // Place codes related to redux.
β”‚   β”œβ”€β”€ res // Place local resources, such as pictures and SVG path.
β”‚   └── utils // Place common tools and methods that are used on the panel.

Code snippet #8

Plain text
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ api // Place a series of cloud APIs used in the project.
β”‚   β”œβ”€β”€ components // Place reusable functional components used in the project.
β”‚   β”œβ”€β”€ composeLayout.tsx // Encapsulate device events and device information required in the panel.
β”‚   β”œβ”€β”€ config // Place frequently-used configuration files.
β”‚   β”œβ”€β”€ containers // Place page-level components of the project.
β”‚   β”œβ”€β”€ i18n // Place configuration files for multiple languages.
β”‚   β”œβ”€β”€ main.tsx // The project entry files that are inherited from NavigatorLayout. Rewrite the hookRoute method to define necessary configurations, such as the background and top bar. Rewrite the renderScene method to control routing jump.
β”‚   β”œβ”€β”€ redux // Place codes related to redux.
β”‚   β”œβ”€β”€ res // Place local resources, such as pictures and SVG path.
β”‚   └── utils // Place common tools and methods that are used on the panel.

Code snippet #9

Plain text
	import PropTypes, { number } from 'prop-types';
	import React, { Component } from 'react';
	import { View, Image, PanResponder, StyleSheet, ViewStyle, ViewPropTypes } from 'react-native';
	import { Utils } from 'tuya-panel-kit';

	export default class HuePicker extends Component {
	static propTypes = {
		accessibilityLabel: PropTypes.string,
		style: ViewPropTypes.style,
		disabled: PropTypes.bool,
		radius: PropTypes.number,
		innerRadius: PropTypes.number,
		thumbRadius: PropTypes.number,
		thumbInnerRadius: PropTypes.number,
		RingBackground: PropTypes.oneOfType([PropTypes.number, PropTypes.string, React.ReactElement]),
		hue: PropTypes.number,
		onValueChange: PropTypes.func,
		onComplete: PropTypes.func,
	};

	static defaultProps = {
		accessibilityLabel: 'HuePicker',
		style: null,
		disabled: false,
		radius: 130,
		innerRadius: 70,
		thumbRadius: 25,
		thumbInnerRadius: 20,
		RingBackground: require('./lamp_colorBg.png'),
		hue: 0,
		onValueChange: () => {},
		onComplete: () => {},
	};

	constructor(props) {
		super(props);
const { radius, thumbRadius } = props;
		this.cx = radius - thumbRadius;
		this.cy = radius - thumbRadius;
		// Define a fixed distance from the drawable ball to the origin so that the ball is always centered in the color wheel.
		this.fixedLength = radius - this.ringSize * 0.5;
		this._panResponder = PanResponder.create({
		onStartShouldSetPanResponder: this.shouldSetResponder,
		onMoveShouldSetPanResponder: this.shouldSetResponder,
		onPanResponderGrant: this._handleResponderGrant,
		onPanResponderMove: this._handleResponderMove,
		onPanResponderRelease: this._handleResponderRelease,
		onPanResponderTerminationRequest: () => false,
		onPanResponderTerminate: this._handleResponderRelease,
		// onStartShouldSetResponderCapture: () => false,
		onMoveShouldSetPanResponderCapture: () => false,
		});
	}

	componentWillReceiveProps(nextProps) {
		const { radius, innerRadius, thumbRadius } = nextProps;
const {
		radius: prevRadius,
		innerRadius: prevInnerRadius,
		thumbRadius: prevThumbRadius,
		} = this.props;
		if (
		prevRadius !== radius ||
		prevInnerRadius !== innerRadius ||
		prevThumbRadius !== thumbRadius
		) {
		this.cx = radius - thumbRadius;
		this.cy = radius - thumbRadius;
		this.fixedLength = radius - this.ringSize * 0.5;
		}
	}

	// The wheel size.
	get ringSize() {
		const { radius, innerRadius } = this.props;
return radius - innerRadius;
	}

	getRadianByCoord(xRelativeOrigin, yRelativeOrigin) {
		const { thumbRadius } = this.props;
		const xRelativeCenter = xRelativeOrigin - this.cx - thumbRadius;
const yRelativeCenter = yRelativeOrigin - this.cy - thumbRadius;
		let rad = Math.atan2(yRelativeCenter, xRelativeCenter);
		if (xRelativeCenter > 0 && yRelativeCenter > 0) rad = Math.PI * 2 - rad;
		if (xRelativeCenter < 0 && yRelativeCenter > 0) rad = Math.PI * 2 - rad;
		if (xRelativeCenter < 0 && yRelativeCenter < 0) rad = Math.abs(rad);
		if (xRelativeCenter > 0 && yRelativeCenter < 0) rad = Math.abs(rad);
		if (xRelativeCenter === 0 && yRelativeCenter > 0) rad = (Math.PI * 3) / 2;
		if (xRelativeCenter === 0 && yRelativeCenter < 0) rad = Math.PI / 2;
		return rad;
	}

	getHueByCoord(xRelativeOrigin, yRelativeOrigin) {
		// 0 to 2 Pi
		const rad = this.getRadianByCoord(xRelativeOrigin, yRelativeOrigin);
return (rad * 180) / Math.PI;
	}

	getCoordByHue(hue) {
		const rad = ((360 - hue) * Math.PI) / 180;
		const x = this.cx + this.fixedLength * Math.cos(rad);
const y = this.cy + this.fixedLength * Math.sin(rad);
		return { x, y };
	}

	getColorInfoByHue(hue) {
		const { r, g, b } = Utils.ColorUtils.hsvToRgb(hue, 1, 1);
		return {
	r,
		g,
		b,
		rgbString: `rgb(${r}, ${g}, ${b})`,
		};
	}
	thumbRef;
	thumbInnerRef;
	cx;
	cy;
	fixedLength;
	_panResponder;
	xRelativeOriginStart;
	yRelativeOriginStart;

	shouldSetResponder = e => {
		// eslint-disable-next-line react/destructuring-assignment
		if (this.props.disabled) {
	return false;
		}
		const { locationX, locationY } = e.nativeEvent;
		// Whether the motion event is within the touchable area.
		const { innerRadius, radius, thumbRadius } = this.props;
		const xRelativeCenter = locationX - this.cx - thumbRadius;
		const yRelativeCenter = locationY - this.cy - thumbRadius;
		const len = Math.sqrt(xRelativeCenter ** 2 + yRelativeCenter ** 2);
		if (len >= innerRadius && len <= radius) {
		return true;
		}
		return false;
	};

	_moveTo(xRelativeOrigin, yRelativeOrigin, callback) {
		const hue = Math.round(this.getHueByCoord(xRelativeOrigin, yRelativeOrigin));
		const { x = 0, y = 0 } = this.getCoordByHue(hue);
const color = this.getColorInfoByHue(hue);
		this.updateThumbStyle({
		transform: [
			{
			translateX: x,
			},
			{
			translateY: y,
			},
		],
		});
		this.updateThumbInnerStyle({
		backgroundColor: color.rgbString,
		});
		typeof callback === 'function' && callback(hue, color);
	}

	_handleResponderGrant = e => {
		const { locationX, locationY } = e.nativeEvent;
		this.xRelativeOriginStart = locationX;
this.yRelativeOriginStart = locationY;
	};

	_handleResponderMove = (e, gestureState) => {
		const { dx, dy } = gestureState;
		const { onValueChange } = this.props;
const xRelativeOrigin = this.xRelativeOriginStart + dx;
		const yRelativeOrigin = this.yRelativeOriginStart + dy;
		this._moveTo(xRelativeOrigin, yRelativeOrigin, onValueChange);
	};

	_handleResponderRelease = (e, gestureState) => {
		const { dx, dy } = gestureState;
		const { onComplete } = this.props;
const xRelativeOrigin = this.xRelativeOriginStart + dx;
		const yRelativeOrigin = this.yRelativeOriginStart + dy;
		// eslint-disable-next-line no-undef
		this._moveTo(xRelativeOrigin, yRelativeOrigin, onComplete);
		this.xRelativeOriginStart = 0;
		this.yRelativeOriginStart = 0;
	};

	updateThumbStyle(style) {
		if (this.thumbRef) {
		this.thumbRef.setNativeProps({ style });
}
	}

	updateThumbInnerStyle(style) {
		if (this.thumbInnerRef) {
		this.thumbInnerRef.setNativeProps({ style });
}
	}

	renderRingBackground() {
		const { radius, RingBackground } = this.props;
		if (typeof RingBackground === 'number') {
	return (
			<Image
			style={{
				width: radius * 2,
				height: radius * 2,
				borderRadius: radius,
			}}
			source={RingBackground}
			/>
		);
		}
		if (React.isValidElement(RingBackground)) {
		return React.cloneElement(RingBackground, {
			style: {
			width: radius * 2,
			height: radius * 2,
			borderRadius: radius,
			...RingBackground.props.style,
			},
		});
		}
	}

	render() {
		const {
		accessibilityLabel,
	style,
		disabled,
		radius,
		thumbRadius,
		thumbInnerRadius,
		hue,
		} = this.props;
		const { x = 0, y = 0 } = this.getCoordByHue(hue);
		const { rgbString } = this.getColorInfoByHue(hue);
		return (
		<View
			accessibilityLabel={accessibilityLabel}
			style={style}
			pointerEvents="box-only"
			{...this._panResponder.panHandlers}
		>
			{/* Wheel */}
			<View style={[styles.sectionRing, { width: radius * 2, height: radius * 2 }]}>
			{this.renderRingBackground()}
			</View>

			{/* Ball */}
			<View
			ref={ref => {
		this.thumbRef = ref;
			}}
			style={[
				styles.sectionThumb,
				{
				width: thumbRadius * 2,
				height: thumbRadius * 2,
				borderRadius: thumbRadius,
				opacity: disabled ? 0 : 1,
				transform: [
					{ translateX: x, },
					{ ranslateY: y, },
				],
				},
			]}
			>
			<View
				ref={ref => {
				this.thumbInnerRef = ref;
				}}
				style={{
				width: thumbInnerRadius * 2,
				height: thumbInnerRadius * 2,
				borderRadius: thumbInnerRadius,
				backgroundColor: rgbString,
				}}
				/>
			</View>
		</View>
		);
	}
	}

	const styles = StyleSheet.create({
	sectionRing: {
		alignItems: 'center',
		justifyContent: 'center',
	},
	sectionThumb: {
		alignItems: 'center',
justifyContent: 'center',
		position: 'absolute',
		backgroundColor: '#fff',
		shadowOffset: { width: 2, height: 2 },
		shadowColor: '#000',
		shadowOpacity: 0.5,
		elevation: 2,
		transform: [
		{ translateX: 0, },
		{ translateY: 0, },
		],
	},
	});

Code snippet #10

Plain text
	import PropTypes, { number } from 'prop-types';
	import React, { Component } from 'react';
	import { View, Image, PanResponder, StyleSheet, ViewStyle, ViewPropTypes } from 'react-native';
	import { Utils } from 'tuya-panel-kit';

	export default class HuePicker extends Component {
	static propTypes = {
		accessibilityLabel: PropTypes.string,
		style: ViewPropTypes.style,
		disabled: PropTypes.bool,
		radius: PropTypes.number,
		innerRadius: PropTypes.number,
		thumbRadius: PropTypes.number,
		thumbInnerRadius: PropTypes.number,
		RingBackground: PropTypes.oneOfType([PropTypes.number, PropTypes.string, React.ReactElement]),
		hue: PropTypes.number,
		onValueChange: PropTypes.func,
		onComplete: PropTypes.func,
	};

	static defaultProps = {
		accessibilityLabel: 'HuePicker',
		style: null,
		disabled: false,
		radius: 130,
		innerRadius: 70,
		thumbRadius: 25,
		thumbInnerRadius: 20,
		RingBackground: require('./lamp_colorBg.png'),
		hue: 0,
		onValueChange: () => {},
		onComplete: () => {},
	};

	constructor(props) {
		super(props);
const { radius, thumbRadius } = props;
		this.cx = radius - thumbRadius;
		this.cy = radius - thumbRadius;
		// Define a fixed distance from the drawable ball to the origin so that the ball is always centered in the color wheel.
		this.fixedLength = radius - this.ringSize * 0.5;
		this._panResponder = PanResponder.create({
		onStartShouldSetPanResponder: this.shouldSetResponder,
		onMoveShouldSetPanResponder: this.shouldSetResponder,
		onPanResponderGrant: this._handleResponderGrant,
		onPanResponderMove: this._handleResponderMove,
		onPanResponderRelease: this._handleResponderRelease,
		onPanResponderTerminationRequest: () => false,
		onPanResponderTerminate: this._handleResponderRelease,
		// onStartShouldSetResponderCapture: () => false,
		onMoveShouldSetPanResponderCapture: () => false,
		});
	}

	componentWillReceiveProps(nextProps) {
		const { radius, innerRadius, thumbRadius } = nextProps;
const {
		radius: prevRadius,
		innerRadius: prevInnerRadius,
		thumbRadius: prevThumbRadius,
		} = this.props;
		if (
		prevRadius !== radius ||
		prevInnerRadius !== innerRadius ||
		prevThumbRadius !== thumbRadius
		) {
		this.cx = radius - thumbRadius;
		this.cy = radius - thumbRadius;
		this.fixedLength = radius - this.ringSize * 0.5;
		}
	}

	// The wheel size.
	get ringSize() {
		const { radius, innerRadius } = this.props;
return radius - innerRadius;
	}

	getRadianByCoord(xRelativeOrigin, yRelativeOrigin) {
		const { thumbRadius } = this.props;
		const xRelativeCenter = xRelativeOrigin - this.cx - thumbRadius;
const yRelativeCenter = yRelativeOrigin - this.cy - thumbRadius;
		let rad = Math.atan2(yRelativeCenter, xRelativeCenter);
		if (xRelativeCenter > 0 && yRelativeCenter > 0) rad = Math.PI * 2 - rad;
		if (xRelativeCenter < 0 && yRelativeCenter > 0) rad = Math.PI * 2 - rad;
		if (xRelativeCenter < 0 && yRelativeCenter < 0) rad = Math.abs(rad);
		if (xRelativeCenter > 0 && yRelativeCenter < 0) rad = Math.abs(rad);
		if (xRelativeCenter === 0 && yRelativeCenter > 0) rad = (Math.PI * 3) / 2;
		if (xRelativeCenter === 0 && yRelativeCenter < 0) rad = Math.PI / 2;
		return rad;
	}

	getHueByCoord(xRelativeOrigin, yRelativeOrigin) {
		// 0 to 2 Pi
		const rad = this.getRadianByCoord(xRelativeOrigin, yRelativeOrigin);
return (rad * 180) / Math.PI;
	}

	getCoordByHue(hue) {
		const rad = ((360 - hue) * Math.PI) / 180;
		const x = this.cx + this.fixedLength * Math.cos(rad);
const y = this.cy + this.fixedLength * Math.sin(rad);
		return { x, y };
	}

	getColorInfoByHue(hue) {
		const { r, g, b } = Utils.ColorUtils.hsvToRgb(hue, 1, 1);
		return {
	r,
		g,
		b,
		rgbString: `rgb(${r}, ${g}, ${b})`,
		};
	}
	thumbRef;
	thumbInnerRef;
	cx;
	cy;
	fixedLength;
	_panResponder;
	xRelativeOriginStart;
	yRelativeOriginStart;

	shouldSetResponder = e => {
		// eslint-disable-next-line react/destructuring-assignment
		if (this.props.disabled) {
	return false;
		}
		const { locationX, locationY } = e.nativeEvent;
		// Whether the motion event is within the touchable area.
		const { innerRadius, radius, thumbRadius } = this.props;
		const xRelativeCenter = locationX - this.cx - thumbRadius;
		const yRelativeCenter = locationY - this.cy - thumbRadius;
		const len = Math.sqrt(xRelativeCenter ** 2 + yRelativeCenter ** 2);
		if (len >= innerRadius && len <= radius) {
		return true;
		}
		return false;
	};

	_moveTo(xRelativeOrigin, yRelativeOrigin, callback) {
		const hue = Math.round(this.getHueByCoord(xRelativeOrigin, yRelativeOrigin));
		const { x = 0, y = 0 } = this.getCoordByHue(hue);
const color = this.getColorInfoByHue(hue);
		this.updateThumbStyle({
		transform: [
			{
			translateX: x,
			},
			{
			translateY: y,
			},
		],
		});
		this.updateThumbInnerStyle({
		backgroundColor: color.rgbString,
		});
		typeof callback === 'function' && callback(hue, color);
	}

	_handleResponderGrant = e => {
		const { locationX, locationY } = e.nativeEvent;
		this.xRelativeOriginStart = locationX;
this.yRelativeOriginStart = locationY;
	};

	_handleResponderMove = (e, gestureState) => {
		const { dx, dy } = gestureState;
		const { onValueChange } = this.props;
const xRelativeOrigin = this.xRelativeOriginStart + dx;
		const yRelativeOrigin = this.yRelativeOriginStart + dy;
		this._moveTo(xRelativeOrigin, yRelativeOrigin, onValueChange);
	};

	_handleResponderRelease = (e, gestureState) => {
		const { dx, dy } = gestureState;
		const { onComplete } = this.props;
const xRelativeOrigin = this.xRelativeOriginStart + dx;
		const yRelativeOrigin = this.yRelativeOriginStart + dy;
		// eslint-disable-next-line no-undef
		this._moveTo(xRelativeOrigin, yRelativeOrigin, onComplete);
		this.xRelativeOriginStart = 0;
		this.yRelativeOriginStart = 0;
	};

	updateThumbStyle(style) {
		if (this.thumbRef) {
		this.thumbRef.setNativeProps({ style });
}
	}

	updateThumbInnerStyle(style) {
		if (this.thumbInnerRef) {
		this.thumbInnerRef.setNativeProps({ style });
}
	}

	renderRingBackground() {
		const { radius, RingBackground } = this.props;
		if (typeof RingBackground === 'number') {
	return (
			<Image
			style={{
				width: radius * 2,
				height: radius * 2,
				borderRadius: radius,
			}}
			source={RingBackground}
			/>
		);
		}
		if (React.isValidElement(RingBackground)) {
		return React.cloneElement(RingBackground, {
			style: {
			width: radius * 2,
			height: radius * 2,
			borderRadius: radius,
			...RingBackground.props.style,
			},
		});
		}
	}

	render() {
		const {
		accessibilityLabel,
	style,
		disabled,
		radius,
		thumbRadius,
		thumbInnerRadius,
		hue,
		} = this.props;
		const { x = 0, y = 0 } = this.getCoordByHue(hue);
		const { rgbString } = this.getColorInfoByHue(hue);
		return (
		<View
			accessibilityLabel={accessibilityLabel}
			style={style}
			pointerEvents="box-only"
			{...this._panResponder.panHandlers}
		>
			{/* Wheel */}
			<View style={[styles.sectionRing, { width: radius * 2, height: radius * 2 }]}>
			{this.renderRingBackground()}
			</View>

			{/* Ball */}
			<View
			ref={ref => {
		this.thumbRef = ref;
			}}
			style={[
				styles.sectionThumb,
				{
				width: thumbRadius * 2,
				height: thumbRadius * 2,
				borderRadius: thumbRadius,
				opacity: disabled ? 0 : 1,
				transform: [
					{ translateX: x, },
					{ ranslateY: y, },
				],
				},
			]}
			>
			<View
				ref={ref => {
				this.thumbInnerRef = ref;
				}}
				style={{
				width: thumbInnerRadius * 2,
				height: thumbInnerRadius * 2,
				borderRadius: thumbInnerRadius,
				backgroundColor: rgbString,
				}}
				/>
			</View>
		</View>
		);
	}
	}

	const styles = StyleSheet.create({
	sectionRing: {
		alignItems: 'center',
		justifyContent: 'center',
	},
	sectionThumb: {
		alignItems: 'center',
justifyContent: 'center',
		position: 'absolute',
		backgroundColor: '#fff',
		shadowOffset: { width: 2, height: 2 },
		shadowColor: '#000',
		shadowOpacity: 0.5,
		elevation: 2,
		transform: [
		{ translateX: 0, },
		{ translateY: 0, },
		],
	},
	});

Github file

https://github.com/tuya/tuya-panel-cli/blob/main/README.md#iwr-cannot-be-recognized-on-windows

Credits

Sandwich IoT
40 projects β€’ 5 followers

Comments