@ledvance/group-ui-biz-bundle 1.0.120 → 1.0.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/modules/biorhythm/BiorhythmPage.tsx +118 -78
  3. package/src/modules/biorhythm/circular/RhythmsCircle.tsx +483 -0
  4. package/src/modules/biorhythm/circular/conical-gradient/Android.tsx +63 -0
  5. package/src/modules/biorhythm/circular/conical-gradient/Ios.tsx +26 -0
  6. package/src/modules/biorhythm/circular/conical-gradient/Normal.tsx +187 -0
  7. package/src/modules/biorhythm/circular/conical-gradient/index.android.tsx +164 -0
  8. package/src/modules/biorhythm/circular/conical-gradient/index.ios.tsx +124 -0
  9. package/src/modules/biorhythm/circular/conical-gradient/index.tsx +3 -0
  10. package/src/modules/biorhythm/circular/conical-gradient/index.web.tsx +94 -0
  11. package/src/modules/biorhythm/circular/conical-gradient/interface.ts +19 -0
  12. package/src/modules/biorhythm/circular/conical-gradient/utils.ts +25 -0
  13. package/src/modules/biorhythm/circular/interface.ts +114 -0
  14. package/src/modules/diyScene/DiySceneEditorPage.tsx +4 -3
  15. package/src/modules/energyConsumption/component/EnergyModal.tsx +3 -0
  16. package/src/modules/lightMode/LightModePage.tsx +50 -144
  17. package/src/modules/mood/DynamicMoodEditorPage.tsx +1 -1
  18. package/src/modules/mood/StaticMoodEditorPage.tsx +1 -1
  19. package/src/modules/mood_new/DynamicMoodEditorPage.tsx +1 -1
  20. package/src/modules/mood_new/MixDynamicMoodEditor.tsx +1 -1
  21. package/src/modules/mood_new/MoodInfo.ts +1 -1
  22. package/src/modules/mood_new/StaticMoodEditorPage.tsx +1 -1
  23. package/src/modules/powerOnBehavior/LightBehaviorPage.tsx +208 -0
  24. package/src/modules/powerOnBehavior/PlugBehaviorPage.tsx +99 -0
  25. package/src/modules/powerOnBehavior/PowerOnBehaviorActions.ts +131 -0
  26. package/src/modules/powerOnBehavior/Router.ts +27 -0
  27. package/src/navigation/Routers.ts +3 -1
  28. package/src/modules/biorhythm/circular/ItemIcon.d.ts +0 -22
  29. package/src/modules/biorhythm/circular/ItemIcon.tsx +0 -173
  30. package/src/modules/biorhythm/circular/Progress.d.ts +0 -24
  31. package/src/modules/biorhythm/circular/Progress.tsx +0 -372
  32. package/src/modules/biorhythm/circular/TimeCircular.d.ts +0 -11
  33. package/src/modules/biorhythm/circular/TimeCircular.tsx +0 -64
@@ -0,0 +1,483 @@
1
+ /* eslint-disable @typescript-eslint/ban-types */
2
+ /* eslint-disable no-return-assign */
3
+ /* eslint-disable no-lonely-if */
4
+ import React, { useRef, useEffect, useState, useMemo } from 'react';
5
+ import {
6
+ View,
7
+ StyleSheet,
8
+ Image,
9
+ PanResponder,
10
+ GestureResponderEvent,
11
+ PanResponderGestureState,
12
+ } from 'react-native';
13
+ import { usePersistFn } from 'ahooks';
14
+ import ConicalGradient from './conical-gradient';
15
+ import { IData, RhythmsCircleProps } from './interface';
16
+ import { Utils } from 'tuya-panel-kit';
17
+ const { convertX: cx } = Utils.RatioUtils
18
+
19
+ const totalTime = 24 * 60;
20
+ const fullDeg = Math.PI * 2;
21
+
22
+ // 获取图标的x,y坐标
23
+ const getPoint = (deg: number, radius: number, center: number) => {
24
+ return {
25
+ x: radius * Math.sin(deg) + center,
26
+ y: -radius * Math.cos(deg) + center,
27
+ };
28
+ };
29
+
30
+ const RhythmsCircle: React.FC<RhythmsCircleProps & { gradientMode?: boolean }> = props => {
31
+ const {
32
+ thumbSize,
33
+ thumbStyle,
34
+ disabled = false,
35
+ disabledOpacity = 1,
36
+ iconStyle,
37
+ timeStyle,
38
+ pickerStyle,
39
+ timeImg,
40
+ lastRingColor,
41
+ gradientMode = true,
42
+ } = props;
43
+
44
+ const dragEnabled = useRef(false);
45
+
46
+ const markRefs = useRef<any>([]).current;
47
+
48
+ const markImgRefs = useRef<any>([]).current;
49
+
50
+ const dragIndex = useRef(0);
51
+
52
+ const outerRadius = useRef(props.size / 2);
53
+
54
+ const size = useRef(props.size);
55
+
56
+ const ringWidth = useRef(props.ringWidth);
57
+
58
+ const innerSize = useRef(props.size - props.ringWidth * 2);
59
+
60
+ const innerRadius = useRef(innerSize.current / 2);
61
+
62
+ const ringRadius = useRef((innerRadius.current + outerRadius.current) / 2);
63
+
64
+ const ringRef = useRef<ConicalGradient>();
65
+
66
+ const initData = (v: IData[]) => {
67
+ return v.map(({ index, time, icon, color, noActiveColor, activeColor }) => ({
68
+ index,
69
+ time,
70
+ icon,
71
+ color,
72
+ noActiveColor,
73
+ activeColor,
74
+ deg: (time / totalTime) * fullDeg,
75
+ }));
76
+ };
77
+
78
+ const [allData, setAllData] = useState(() => initData(props.data || []));
79
+
80
+ useEffect(() => {
81
+ size.current = props.size;
82
+ ringWidth.current = props.ringWidth;
83
+ innerSize.current = props.size - props.ringWidth * 2;
84
+ innerRadius.current = innerSize.current / 2;
85
+ outerRadius.current = props.size / 2;
86
+ ringRadius.current = (innerRadius.current + outerRadius.current) / 2;
87
+ }, [props.size, props.ringWidth]);
88
+
89
+ useEffect(() => {
90
+ setAllData(() => initData(props.data || []));
91
+ }, [props.data]);
92
+
93
+ // 极坐标的渐变色 - 修改以支持两种模式
94
+ const getRingColors = (data: IData[]) => {
95
+ type ColorStop = { color: string; angle: number };
96
+
97
+ // 角度规范化函数:确保角度在0到2π范围内
98
+ const normalizeAngle = (angle: number) => {
99
+ while (angle < 0) {
100
+ angle += fullDeg;
101
+ }
102
+ while (angle >= fullDeg) {
103
+ angle -= fullDeg;
104
+ }
105
+ return angle;
106
+ };
107
+
108
+ if (gradientMode) {
109
+ // 渐变模式:原有逻辑
110
+ const colors: ColorStop[] = data.map(({ time, color }) => ({
111
+ color,
112
+ angle: normalizeAngle((time / totalTime) * fullDeg - Math.PI / 2),
113
+ }));
114
+
115
+ // 在日落节点后多加一个颜色节点优化显示
116
+ colors.push({
117
+ color: lastRingColor || '#000',
118
+ angle: normalizeAngle(((data[0].time - 60) / totalTime) * fullDeg - Math.PI / 2),
119
+ });
120
+
121
+ colors.sort((a, b) => {
122
+ return a.angle - b.angle;
123
+ });
124
+ return colors;
125
+ } else {
126
+ // 直接变化模式:创建阶梯式颜色变化,消除渐变效果
127
+ const sortedData = [...data].sort((a, b) => a.time - b.time);
128
+ const colors: ColorStop[] = [];
129
+
130
+ for (let i = 0; i < sortedData.length; i++) {
131
+ const currentItem = sortedData[i];
132
+ const nextItem = sortedData[(i + 1) % sortedData.length];
133
+
134
+ // 当前节点的开始角度
135
+ const currentAngle = normalizeAngle((currentItem.time / totalTime) * fullDeg - Math.PI / 2);
136
+
137
+ // 下一个节点的角度
138
+ let nextAngle = normalizeAngle((nextItem.time / totalTime) * fullDeg - Math.PI / 2);
139
+
140
+ // 处理跨越0点的情况
141
+ if (nextAngle <= currentAngle) {
142
+ nextAngle += fullDeg;
143
+ }
144
+
145
+ // 在当前颜色区间内创建多个相同颜色的点来消除渐变
146
+ const segmentLength = nextAngle - currentAngle;
147
+ const numSegments = Math.max(10, Math.floor(segmentLength / (fullDeg / 100))); // 至少10个点
148
+
149
+ for (let j = 0; j < numSegments; j++) {
150
+ const angle = currentAngle + (segmentLength * j / numSegments);
151
+ colors.push({
152
+ color: currentItem.color,
153
+ angle: normalizeAngle(angle),
154
+ });
155
+ }
156
+
157
+ // 在下一个节点前的最后一个点
158
+ if (i === sortedData.length - 1) {
159
+ // 最后一个段,延伸到第一个节点
160
+ colors.push({
161
+ color: currentItem.color,
162
+ angle: normalizeAngle(nextAngle - 0.001),
163
+ });
164
+ } else {
165
+ colors.push({
166
+ color: currentItem.color,
167
+ angle: normalizeAngle(nextAngle - 0.001),
168
+ });
169
+ }
170
+ }
171
+
172
+ return colors;
173
+ }
174
+ };
175
+
176
+ // 是否成为响应者
177
+ const handleSetPanResponder = usePersistFn((e: GestureResponderEvent) => {
178
+ // 如果传过来的disabled属性为true,则返回false阻止成为响应者
179
+ if (disabled) {
180
+ return false;
181
+ }
182
+ // 拿到触摸点相对于父元素的横纵坐标
183
+ const { locationX, locationY } = e.nativeEvent;
184
+ let selectIndex = -1;
185
+ const exist = allData.some(({ deg }, index) => {
186
+ const { x, y } = getPoint(deg, cx(ringRadius.current), cx(outerRadius.current));
187
+ const length = Math.sqrt((x - locationX) ** 2 + (y - locationY) ** 2);
188
+ if (length <= thumbSize / 2) {
189
+ selectIndex = index;
190
+ dragEnabled.current = true;
191
+ return true;
192
+ }
193
+ return false;
194
+ });
195
+ dragIndex.current = selectIndex;
196
+ if (exist) {
197
+ return true;
198
+ }
199
+ return true;
200
+ });
201
+
202
+ const moveMark = usePersistFn(
203
+ ({ dx, dy }: PanResponderGestureState, callback: Function | null, needUpdateState: boolean) => {
204
+ const dragInx = dragIndex.current;
205
+ const rinRad = cx(ringRadius.current);
206
+ const outRad = cx(outerRadius.current);
207
+ const mark = allData[dragInx];
208
+ const markRef = markRefs[dragInx];
209
+ if (!mark || !markRef) {
210
+ return;
211
+ }
212
+
213
+ const markImgRef = markImgRefs[dragInx];
214
+ markImgRef.setNativeProps({
215
+ style: {
216
+ tintColor: allData[dragInx].activeColor,
217
+ },
218
+ });
219
+ const { deg } = mark;
220
+ const { x, y } = getPoint(deg, rinRad, outRad);
221
+ const newX = x + dx;
222
+ const newY = y + dy;
223
+ // const length = Math.sqrt((newX - outRad) ** 2 + (newY - outRad) ** 2);
224
+ let angle = Math.atan2(newX - outRad, -(newY - outRad));
225
+ if (angle < 0) {
226
+ angle += fullDeg;
227
+ }
228
+
229
+ // 角度对应的时间
230
+ let time = Math.floor((angle * totalTime) / fullDeg);
231
+ // 是否是允许的时间范围
232
+ time = handleStepOver(time);
233
+ // 转回成角度,确保不出现跳动问题
234
+ angle = (time / totalTime) * fullDeg;
235
+ const point = getPoint(angle, rinRad, outRad);
236
+ markRef.setNativeProps({
237
+ style: {
238
+ top: point.y,
239
+ left: point.x,
240
+ },
241
+ });
242
+
243
+ const newData = allData.map((item, index) => {
244
+ if (index !== dragInx) {
245
+ return item;
246
+ }
247
+ return {
248
+ ...item,
249
+ deg: angle,
250
+ time,
251
+ };
252
+ });
253
+ callback && callback(newData, dragInx);
254
+ // onChange
255
+ props.onChange && props.onChange(newData);
256
+
257
+ ringRef?.current?.setColors(getRingColors(newData));
258
+ if (needUpdateState) {
259
+ setAllData(newData);
260
+ }
261
+ }
262
+ );
263
+
264
+ const handleStepOver = (time: number) => {
265
+ let newTime = time;
266
+ if (allData.length === 1) {
267
+ return newTime;
268
+ }
269
+
270
+ const dragInx = dragIndex.current;
271
+ const currentTime = allData[dragInx].time;
272
+ const MIN_DISTANCE = 15; // 定义最小距离为15分钟
273
+
274
+ // 如果几乎没有移动,直接返回原时间
275
+ if (Math.abs(time - currentTime) < 2) {
276
+ return currentTime;
277
+ }
278
+
279
+ // 找出所有其他节点的时间
280
+ const otherTimes = allData
281
+ .filter((_, index) => index !== dragInx)
282
+ .map(item => item.time);
283
+
284
+ // 检查是否接近其他节点
285
+ for (let i = 0; i < otherTimes.length; i++) {
286
+ const otherTime = otherTimes[i];
287
+
288
+ // 计算距离(考虑圆形24小时的特性)
289
+ let distance = Math.abs(newTime - otherTime);
290
+ if (distance > totalTime / 2) {
291
+ distance = totalTime - distance; // 取较短的距离
292
+ }
293
+
294
+ // 如果距离小于MIN_DISTANCE,调整位置
295
+ if (distance < MIN_DISTANCE) {
296
+ // 确定调整方向
297
+ const direction = ((newTime - otherTime + totalTime) % totalTime) < totalTime / 2 ? 1 : -1;
298
+
299
+ // 计算新位置,保持15分钟距离
300
+ newTime = (otherTime + direction * MIN_DISTANCE + totalTime) % totalTime;
301
+
302
+ // 检查新位置是否与其他节点冲突
303
+ let hasConflict = false;
304
+ for (let j = 0; j < otherTimes.length; j++) {
305
+ if (j !== i) {
306
+ const checkTime = otherTimes[j];
307
+ let checkDistance = Math.abs(newTime - checkTime);
308
+ if (checkDistance > totalTime / 2) {
309
+ checkDistance = totalTime - checkDistance;
310
+ }
311
+
312
+ if (checkDistance < MIN_DISTANCE) {
313
+ hasConflict = true;
314
+ break;
315
+ }
316
+ }
317
+ }
318
+
319
+ // 如果新位置仍有冲突,回到原位置
320
+ if (hasConflict) {
321
+ newTime = currentTime;
322
+ }
323
+
324
+ break;
325
+ }
326
+ }
327
+
328
+ return newTime;
329
+ };
330
+
331
+ const onMove = usePersistFn((_: GestureResponderEvent, g: PanResponderGestureState) => {
332
+ moveMark(g, props.onMove || null, false);
333
+ });
334
+
335
+ const onRelease = usePersistFn((_: GestureResponderEvent, g: PanResponderGestureState) => {
336
+ dragEnabled.current = false;
337
+ moveMark(g, props.onRelease || null, true);
338
+ });
339
+
340
+ const _panResponder = useMemo(
341
+ () =>
342
+ PanResponder.create({
343
+ onStartShouldSetPanResponder: handleSetPanResponder,
344
+ // 另一个组件成为新的响应者
345
+ onPanResponderTerminationRequest: () => !(!props.disabled && dragEnabled.current),
346
+ onPanResponderGrant: () => { },
347
+ onPanResponderMove: onMove,
348
+ onPanResponderRelease: onRelease,
349
+ }),
350
+ [props.disabled]
351
+ );
352
+
353
+ const bgSize = innerSize.current - 12;
354
+ return (
355
+ <View
356
+ style={[
357
+ disabled && { opacity: disabledOpacity },
358
+ pickerStyle,
359
+ { borderRadius: cx(outerRadius.current) },
360
+ ]}
361
+ >
362
+ {/* timeStyle */}
363
+ <View
364
+ style={[
365
+ {
366
+ width: cx(size.current),
367
+ height: cx(size.current),
368
+ borderRadius: cx(size.current / 2),
369
+ position: 'relative',
370
+ },
371
+ timeStyle,
372
+ ]}
373
+ >
374
+ {/* ConicalGradient */}
375
+ <ConicalGradient
376
+ ref={(ref: ConicalGradient) => (ringRef.current = ref)}
377
+ innerRadius={cx(innerRadius.current)}
378
+ outerRadius={cx(outerRadius.current)}
379
+ colors={getRingColors(allData)}
380
+ offsetAngle={0}
381
+ segmentAngle={Math.PI / 120}
382
+ />
383
+ {/* timeImg */}
384
+ <Image
385
+ source={timeImg || {}}
386
+ style={{
387
+ position: 'absolute',
388
+ top: '50%',
389
+ left: '50%',
390
+ width: cx(bgSize),
391
+ height: cx(bgSize),
392
+ transform: [{ translateX: -cx(bgSize / 2) }, { translateY: -cx(bgSize / 2) }],
393
+ }}
394
+ resizeMode="contain"
395
+ />
396
+
397
+ {/* Icon */}
398
+ <View style={[styles.thumbBox, { width: cx(size.current), height: cx(size.current) }]}>
399
+ {allData.map((item, index) => {
400
+ const point = getPoint(item.deg, cx(ringRadius.current), cx(outerRadius.current));
401
+ return (
402
+ <View
403
+ key={item.time}
404
+ ref={(ref: View) => (markRefs[index] = ref)}
405
+ style={[
406
+ styles.thumbStyle,
407
+ {
408
+ width: cx(thumbSize),
409
+ height: cx(thumbSize),
410
+ borderRadius: cx(thumbSize / 2),
411
+ transform: [{ translateX: -cx(thumbSize / 2) }, { translateY: -cx(thumbSize / 2) }],
412
+ left: point.x,
413
+ top: point.y,
414
+ justifyContent: 'center',
415
+ alignItems: 'center',
416
+ },
417
+ thumbStyle,
418
+ ]}
419
+ >
420
+ <Image
421
+ source={{ uri: item.icon }}
422
+ ref={(ref: Image) => (markImgRefs[index] = ref)}
423
+ style={[
424
+ {
425
+ width: cx(thumbSize - 10),
426
+ height: cx(thumbSize - 10),
427
+ tintColor: item.noActiveColor,
428
+ },
429
+ iconStyle,
430
+ ]}
431
+ resizeMode="contain"
432
+ />
433
+ </View>
434
+ );
435
+ })}
436
+ </View>
437
+ </View>
438
+ {/* panResponder */}
439
+ <View
440
+ style={[styles.thumbBox, { width: cx(size.current), height: cx(size.current) }]}
441
+ pointerEvents="box-only"
442
+ {..._panResponder.panHandlers}
443
+ />
444
+ </View>
445
+ );
446
+ };
447
+
448
+ RhythmsCircle.defaultProps = {
449
+ size: 250,
450
+ ringWidth: 40,
451
+ thumbSize: 36,
452
+ data: [],
453
+ disabled: false,
454
+ disabledOpacity: 1,
455
+ pickerStyle: {},
456
+ timeStyle: {
457
+ backgroundColor: 'transparent',
458
+ },
459
+ thumbStyle: {
460
+ backgroundColor: '#fff',
461
+ },
462
+ iconStyle: {},
463
+ lastRingColor: '#000',
464
+ gradientMode: true,
465
+ onMove() { },
466
+ onRelease() { },
467
+ onChange() { },
468
+ };
469
+
470
+ export default RhythmsCircle;
471
+
472
+ const styles = StyleSheet.create({
473
+ thumbBox: {
474
+ left: 0,
475
+ position: 'absolute',
476
+ top: 0,
477
+ zIndex: 1,
478
+ },
479
+ thumbStyle: {
480
+ backgroundColor: '#fff',
481
+ position: 'absolute',
482
+ },
483
+ });
@@ -0,0 +1,63 @@
1
+ import React, { PureComponent } from 'react';
2
+ import extractGradient from 'react-native-svg/lib/extract/extractGradient';
3
+ import createReactNativeComponentClass from 'react-native-svg/lib/createReactNativeComponentClass';
4
+ import { ClipPathAttributes } from 'react-native-svg/lib/attributes';
5
+
6
+ function arrayDiffer(a: number[], b: number[]) {
7
+ if (a == null || b == null) {
8
+ return true;
9
+ }
10
+ if (a.length !== b.length) {
11
+ return true;
12
+ }
13
+ for (let i = 0; i < a.length; i++) {
14
+ if (a[i] !== b[i]) {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+
21
+ const GradientAttributes = {
22
+ gradient: {
23
+ diff: arrayDiffer,
24
+ },
25
+ gradientUnits: true,
26
+ gradientTransform: {
27
+ diff: arrayDiffer,
28
+ },
29
+ cx: true,
30
+ cy: true,
31
+ ...ClipPathAttributes,
32
+ };
33
+
34
+ interface Props {
35
+ cx?: number | string;
36
+ cy?: number | string;
37
+ gradientUnits?: 'objectBoundingBox' | 'userSpaceOnUse';
38
+ id?: string;
39
+ }
40
+
41
+ export default class ConicalGradient extends PureComponent<Props> {
42
+ static defaultProps = {
43
+ cx: '50%',
44
+ cy: '50%',
45
+ gradientUnits: 'objectBoundingBox',
46
+ };
47
+
48
+ render() {
49
+ const { cx, cy } = this.props;
50
+ return (
51
+ <RNSVGConicalGradient
52
+ cx={cx.toString()}
53
+ cy={cy.toString()}
54
+ {...extractGradient(this.props)}
55
+ />
56
+ );
57
+ }
58
+ }
59
+
60
+ const RNSVGConicalGradient = createReactNativeComponentClass('CustomRNSVGConicalGradient', () => ({
61
+ validAttributes: GradientAttributes,
62
+ uiViewClassName: 'RNSVGConicalGradient',
63
+ }));
@@ -0,0 +1,26 @@
1
+ import React, { PureComponent } from 'react';
2
+ import { processColor, requireNativeComponent, StyleProp, ViewStyle } from 'react-native';
3
+
4
+ interface Props {
5
+ fromDegree: number;
6
+ colors: number[];
7
+ stops: number[];
8
+ innerRadius: number;
9
+ style: StyleProp<ViewStyle>;
10
+ }
11
+
12
+ const TYRCTConicGradientComp = requireNativeComponent('CustomTYRCTConicGradientView');
13
+
14
+ export default class ConicalGradent extends PureComponent<Props> {
15
+ static defaultProps = {
16
+ fromDegree: 0,
17
+ colors: [processColor('red'), processColor('green')],
18
+ stops: [0, 1],
19
+ innerRadius: 0,
20
+ style: null,
21
+ };
22
+
23
+ render() {
24
+ return <TYRCTConicGradientComp {...this.props} />;
25
+ }
26
+ }