@ledvance/ui-biz-bundle 1.1.70 → 1.1.71

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.
@@ -1,650 +1,650 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
2
- import {
3
- FlatList,
4
- Image,
5
- ScrollView,
6
- StyleSheet,
7
- Text,
8
- TouchableOpacity,
9
- View,
10
- } from 'react-native';
11
- import { Utils } from 'tuya-panel-kit';
12
- import { useReactive } from 'ahooks';
13
- import { cloneDeep, find, isEqual } from 'lodash';
14
- import Page from '@ledvance/base/src/components/Page';
15
- import Strings from '@ledvance/base/src/i18n';
16
- import { StaticMoodEditorPageParams } from './StaticMoodEditorPage';
17
- import { useNavigation, useRoute } from '@react-navigation/native';
18
- import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils';
19
- import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
20
- import res from '@ledvance/base/src/res';
21
- import TextField from '@ledvance/base/src/components/TextField';
22
- import Card from '@ledvance/base/src/components/Card';
23
- import Spacer from '@ledvance/base/src/components/Spacer';
24
- import LampAdjustView from '@ledvance/base/src/components/LampAdjustView';
25
- import LdvSlider from '@ledvance/base/src/components/ldvSlider';
26
- import { useFanMaxSpeed } from '@ledvance/base/src/models/modules/NativePropsSlice';
27
- import TextButton from '@ledvance/base/src/components/TextButton';
28
- import {
29
- MoodNodeTransitionMode,
30
- stripLightMoodMode,
31
- stringLightMoodMode,
32
- lightMoodMode,
33
- StripLightMoodMode,
34
- MoodUIInfo,
35
- } from './Interface';
36
- import TextFieldStyleButton from '@ledvance/base/src/components/TextFieldStyleButton';
37
- import FanAdjustView from '@ledvance/base/src/components/FanAdjustView';
38
- import { ui_biz_routerKey } from '../../navigation/Routers'
39
- import { SelectPageParams } from '@ledvance/ui-biz-bundle/src/modules/select/SelectPage';
40
- import I18n from '@ledvance/base/src/i18n';
41
- import Segmented from '@ledvance/base/src/components/Segmented';
42
- import { MoodNodeInfo } from './Interface';
43
- import { showDialog } from '@ledvance/base/src/utils/common';
44
-
45
- const cx = Utils.RatioUtils.convertX;
46
- interface DynamicMoodEditorPageState {
47
- headline: string;
48
- mood: MoodUIInfo;
49
- mainNode: MoodNodeInfo;
50
- mainBucketSelected: boolean;
51
- sceneMode: StripLightMoodMode;
52
- loading: boolean;
53
- }
54
- const DynamicMoodEditorPage = () => {
55
- const navigation = useNavigation();
56
- const routeParams = useRoute().params as StaticMoodEditorPageParams;
57
- const params = cloneDeep(routeParams);
58
- const moduleParams = params.moduleParams;
59
- const state = useReactive<DynamicMoodEditorPageState>({
60
- headline: '',
61
- mood: params.currentMood,
62
- mainNode: params.currentMood.mainLamp.nodes[params.currentMood.mainLamp.nodes.length - 1],
63
- mainBucketSelected: false,
64
- loading: false,
65
- sceneMode: moduleParams.isStringLight
66
- ? stringLightMoodMode
67
- : moduleParams.isStripLight
68
- ? stripLightMoodMode
69
- : lightMoodMode,
70
- });
71
-
72
- useEffect(() => {
73
- state.headline = Strings.getLang(
74
- params.mode === 'add'
75
- ? 'add_new_dynamic_mood_headline_text'
76
- : 'edit_static_mood_headline_text'
77
- );
78
- }, [params.mode]);
79
-
80
- const getColorBlockColor = useCallback((node: MoodNodeInfo) => {
81
- const s = Math.round(mapFloatToRange(node.s / 100, 30, 100));
82
- if (node.isColorNode) {
83
- return hsv2Hex(node.h, s, 100);
84
- } else {
85
- return cctToColor(node.colorTemp.toFixed());
86
- }
87
- }, []);
88
-
89
- const getNodeColor = useCallback((node: MoodNodeInfo) => {
90
- if (node.isColorNode) {
91
- const s = Math.round(mapFloatToRange(node.s / 100, 30, 100));
92
- return hsv2Hex(node.h, s, 100);
93
- }
94
- return cctToColor(node.colorTemp.toFixed());
95
- }, []);
96
-
97
- const createSelectModeData = useCallback(
98
- (mode: number, moodMode?: StripLightMoodMode) => {
99
- return Object.values(moodMode ? moodMode : state.sceneMode).map(scene => {
100
- return {
101
- text: scene.title,
102
- selected: scene.mode === mode,
103
- value: scene.mode,
104
- };
105
- });
106
- },
107
- [state.sceneMode]
108
- );
109
-
110
- const createSelectOtherData = useCallback((otherData, expand) => {
111
- return otherData.map(other => {
112
- return {
113
- text: other.label,
114
- selected: other.value === expand,
115
- value: other.value,
116
- };
117
- });
118
- }, []);
119
-
120
- const getSelectOther = useCallback((otherData, expand) => {
121
- const currentOther = otherData.find(other => other.value === expand);
122
- return currentOther.label;
123
- }, []);
124
-
125
- const nameRepeat = useMemo(() => {
126
- return params.nameRepeat(state.mood)
127
- }, [state.mood.name]);
128
-
129
- const checkMoodChanged = useMemo(() =>{
130
- return isEqual(state.mood, params.currentMood)
131
- }, [JSON.stringify(state.mood), params.currentMood])
132
-
133
- const canSaveMoodData = useMemo(() =>{
134
- return state.mood.name.length > 0 && state.mood.name.length < 33 && !nameRepeat && (params.mode === 'add' || !checkMoodChanged)
135
- }, [nameRepeat, state.mood.name, checkMoodChanged, params.mode])
136
-
137
- return (
138
- <Page
139
- backText={Strings.getLang('mesh_device_detail_mode')}
140
- showBackDialog={!checkMoodChanged}
141
- backDialogTitle={Strings.getLang(
142
- params.mode === 'add'
143
- ? 'string_light_pp_dialog_sm_add_headline_c'
144
- : 'manage_user_unsaved_changes_dialog_headline'
145
- )}
146
- backDialogContent={Strings.getLang(
147
- params.mode === 'add'
148
- ? 'strip_light_static_mood_add_step_2_dialog_text'
149
- : 'strip_light_static_mood_editor_step_2_dialog_text'
150
- )}
151
- headlineText={state.headline}
152
- rightButtonIcon={canSaveMoodData ? res.ic_check : res.ic_uncheck}
153
- rightButtonIconClick={async () => {
154
- if (state.loading || !canSaveMoodData) return;
155
- state.loading = true;
156
- const res = await params.modDeleteMood(params.mode, cloneDeep(state.mood));
157
- if (res.success) {
158
- navigation.navigate(ui_biz_routerKey.ui_biz_mood);
159
- }
160
- state.loading = false;
161
- }}
162
- loading={state.loading}
163
- >
164
- <ScrollView style={{ flex: 1 }} nestedScrollEnabled={true}>
165
- <View style={styles.root}>
166
- <TextField
167
- style={styles.name}
168
- value={state.mood.name}
169
- placeholder={Strings.getLang('edit_static_mood_inputfield_topic_text')}
170
- onChangeText={text => {
171
- state.mood.name = text;
172
- }}
173
- maxLength={33}
174
- showError={state.mood.name.length > 32 || nameRepeat}
175
- tipColor={nameRepeat ? '#f00' : undefined}
176
- tipIcon={nameRepeat ? res.ic_text_field_input_error : undefined}
177
- errorText={Strings.getLang(
178
- nameRepeat ? 'string_light_pp_field_sm_add_error1' : 'add_new_dynamic_mood_alert_text'
179
- )}
180
- />
181
- <Card style={styles.adjustCard}>
182
- <Spacer height={cx(16)} />
183
- <View style={styles.lightLine}>
184
- <Text style={styles.light}>
185
- {Strings.getLang('light_sources_tile_tw_lighting_headline')}
186
- </Text>
187
- </View>
188
- <Spacer height={cx(18)} />
189
- <TextFieldStyleButton
190
- style={styles.transitionMode}
191
- text={state.sceneMode[state.mood.mainLamp.mode]?.title}
192
- placeholder={Strings.getLang('add_new_dynamic_mood_color_changing_mode_headline')}
193
- onPress={() => {
194
- const paramsSelect: SelectPageParams<number> = {
195
- title: I18n.getLang('add_new_dynamic_mood_color_changing_mode_headline'),
196
- data: createSelectModeData(state.mood.mainLamp.mode),
197
- onSelect: selectPageData => {
198
- state.mood.mainLamp.mode = selectPageData.value;
199
- },
200
- };
201
- navigation.navigate(ui_biz_routerKey.ui_biz_select_page, paramsSelect);
202
- }}
203
- />
204
- <Spacer height={cx(10)} />
205
- <LdvSlider
206
- title={Strings.getLang('add_new_dynamic_mood_lights_field_speed_topic_text')}
207
- value={state.mood.mainLamp.speed}
208
- onValueChange={() => {}}
209
- onSlidingComplete={value => {
210
- state.mood.mainLamp.speed = value;
211
- }}
212
- />
213
- <Spacer height={cx(16)} />
214
- {state.sceneMode[state.mood.mainLamp.mode]?.turnOn && (
215
- <View style={styles.transitionMode}>
216
- <Segmented
217
- value={state.mood.mainLamp.direction}
218
- options={[
219
- {
220
- label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text1'),
221
- value: 0,
222
- },
223
- {
224
- label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text2'),
225
- value: 1,
226
- },
227
- ]}
228
- onChange={v => (state.mood.mainLamp.direction = Number(v))}
229
- />
230
- <Spacer />
231
- </View>
232
- )}
233
- {state.sceneMode[state.mood.mainLamp.mode]?.paragraph && (
234
- <View style={styles.transitionMode}>
235
- <Segmented
236
- value={state.mood.mainLamp.segmented}
237
- options={[
238
- {
239
- label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text3'),
240
- value: 0,
241
- },
242
- {
243
- label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text4'),
244
- value: 1,
245
- },
246
- ]}
247
- onChange={v => (state.mood.mainLamp.segmented = Number(v))}
248
- />
249
- <Spacer />
250
- </View>
251
- )}
252
- {state.sceneMode[state.mood.mainLamp.mode]?.other && (
253
- <>
254
- <TextFieldStyleButton
255
- style={styles.transitionMode}
256
- text={getSelectOther(
257
- state.sceneMode[state.mood.mainLamp.mode]?.other,
258
- state.mood.mainLamp.expand
259
- )}
260
- placeholder={I18n.getLang(
261
- 'add_new_dynamic_mood_strip_lights_selectionfield2_topic_text'
262
- )}
263
- onPress={() => {
264
- const paramsSelect: SelectPageParams<number> = {
265
- title: I18n.getLang(
266
- 'add_new_dynamic_mood_strip_lights_selectionfield2_topic_text'
267
- ),
268
- data: createSelectOtherData(
269
- state.sceneMode[state.mood.mainLamp.mode]?.other,
270
- state.mood.mainLamp.expand
271
- ),
272
- onSelect: selectPageData => {
273
- state.mood.mainLamp.expand = selectPageData.value;
274
- },
275
- };
276
- navigation.navigate(ui_biz_routerKey.ui_biz_select_page, paramsSelect);
277
- }}
278
- />
279
- <Spacer />
280
- </>
281
- )}
282
- <View style={styles.nodesAdjust}>
283
- <View style={styles.adjustButtons}>
284
- <TouchableOpacity
285
- onPress={() => {
286
- state.mainBucketSelected = true;
287
- }}
288
- >
289
- <Image
290
- style={[
291
- styles.adjustButton,
292
- { tintColor: state.mainBucketSelected ? '#f60' : '#666' },
293
- ]}
294
- source={res.ic_paint_bucket}
295
- />
296
- </TouchableOpacity>
297
- <TouchableOpacity
298
- onPress={() => {
299
- state.mainBucketSelected = false;
300
- }}
301
- >
302
- <Image
303
- style={[
304
- styles.adjustButton,
305
- { tintColor: state.mainBucketSelected ? '#666' : '#f60' },
306
- ]}
307
- source={res.ic_colorize}
308
- />
309
- </TouchableOpacity>
310
- </View>
311
- <FlatList
312
- data={state.mood.mainLamp.nodes}
313
- style={styles.nodeList}
314
- renderItem={({ item, index }) => {
315
- return (
316
- <View style={styles.nodeItem}>
317
- <TouchableOpacity
318
- style={[
319
- styles.nodeBlock,
320
- {
321
- backgroundColor: getNodeColor(item),
322
- },
323
- ]}
324
- onPress={() => {
325
- state.mainNode = item;
326
- }}
327
- />
328
- <TouchableOpacity
329
- style={styles.nodeDeleteBtn}
330
- disabled={state.mood.mainLamp.nodes.length < 3}
331
- onPress={() => {
332
- state.mood.mainLamp.nodes.splice(index, 1);
333
- state.mainNode = state.mood.mainLamp.nodes[state.mood.mainLamp.nodes.length - 1];
334
- }}
335
- >
336
- <Image
337
- style={[
338
- styles.nodeDeleteIcon,
339
- {
340
- tintColor: state.mood.mainLamp.nodes.length < 3 ? '#ccc' : '#666',
341
- },
342
- ]}
343
- source={res.ic_mood_del}
344
- />
345
- </TouchableOpacity>
346
- </View>
347
- );
348
- }}
349
- keyExtractor={(_, index) => `${index}`}
350
- ItemSeparatorComponent={() => <Spacer height={cx(12)} />}
351
- ListFooterComponent={() => {
352
- if (state.mood.mainLamp.nodes.length >= 8) {
353
- return <></>;
354
- }
355
- return (
356
- <View>
357
- <Spacer height={cx(12)} />
358
- <TouchableOpacity
359
- style={styles.nodeAddBtn}
360
- onPress={() => {
361
- const node = {
362
- ...state.mainNode,
363
- };
364
- state.mood.mainLamp.nodes.push(node);
365
- state.mainNode = node;
366
- }}
367
- >
368
- <Image
369
- style={{
370
- width: cx(18),
371
- height: cx(18),
372
- tintColor: '#000',
373
- }}
374
- source={{ uri: res.add }}
375
- />
376
- </TouchableOpacity>
377
- </View>
378
- );
379
- }}
380
- />
381
- </View>
382
- <Spacer />
383
- <View style={styles.lightLine}>
384
- <Text style={styles.light}>
385
- {Strings.getLang('add_new_dynamic_mood_lights_field_headline2_text')}
386
- </Text>
387
- <View
388
- style={[styles.preview, { backgroundColor: getColorBlockColor(state.mainNode) }]}
389
- />
390
- </View>
391
- <Spacer />
392
- <LampAdjustView
393
- isSupportColor={moduleParams.isSupportColor}
394
- isSupportBrightness={moduleParams.isSupportBrightness}
395
- isSupportTemperature={moduleParams.isSupportTemperature}
396
- isColorMode={state.mainNode.isColorNode}
397
- reserveSV={true}
398
- setIsColorMode={isColorMode => {
399
- if (state.mainBucketSelected) {
400
- state.mood.mainLamp.nodes.forEach(node => {
401
- node.isColorNode = isColorMode;
402
- if (isColorMode && node.h === 0 && node.s === 0 && node.v === 0) {
403
- node.s = 100;
404
- node.v = 100;
405
- } else {
406
- if (node.brightness === 0 && node.colorTemp === 0) {
407
- node.brightness = 100;
408
- }
409
- }
410
- });
411
- } else {
412
- state.mainNode.isColorNode = isColorMode;
413
- if (
414
- isColorMode &&
415
- state.mainNode.h === 0 &&
416
- state.mainNode.s === 0 &&
417
- state.mainNode.v === 0
418
- ) {
419
- state.mainNode.s = 100;
420
- state.mainNode.v = 100;
421
- } else {
422
- if (state.mainNode.brightness === 0 && state.mainNode.colorTemp === 0) {
423
- state.mainNode.brightness = 100;
424
- }
425
- }
426
- }
427
- }}
428
- h={state.mainNode.h}
429
- s={state.mainNode.s}
430
- v={state.mainNode.v}
431
- onHSVChange={(h, s, v) => {
432
- if (state.mainBucketSelected) {
433
- state.mood.mainLamp.nodes.forEach(node => {
434
- node.isColorNode = true;
435
- node.h = h;
436
- node.s = s;
437
- node.v = v;
438
- });
439
- } else {
440
- state.mainNode.h = h;
441
- state.mainNode.s = s;
442
- state.mainNode.v = v;
443
- }
444
- }}
445
- onHSVChangeComplete={(h, s, v) => {
446
- if (state.mainBucketSelected) {
447
- state.mood.mainLamp.nodes.forEach(node => {
448
- node.isColorNode = true;
449
- node.h = h;
450
- node.s = s;
451
- node.v = v;
452
- });
453
- } else {
454
- state.mainNode.h = h;
455
- state.mainNode.s = s || 1;
456
- state.mainNode.v = v;
457
- }
458
- }}
459
- colorTemp={state.mainNode.colorTemp}
460
- brightness={state.mainNode.brightness}
461
- onCCTChange={cct => {
462
- if (state.mainBucketSelected) {
463
- state.mood.mainLamp.nodes.forEach(node => {
464
- node.isColorNode = false;
465
- node.colorTemp = cct;
466
- });
467
- } else {
468
- state.mainNode.colorTemp = cct;
469
- }
470
- }}
471
- onCCTChangeComplete={cct => {
472
- if (state.mainBucketSelected) {
473
- state.mood.mainLamp.nodes.forEach(node => {
474
- node.isColorNode = false;
475
- node.colorTemp = cct;
476
- });
477
- } else {
478
- state.mainNode.colorTemp = cct;
479
- }
480
- }}
481
- onBrightnessChange={brightness => {
482
- if (state.mainBucketSelected) {
483
- state.mood.mainLamp.nodes.forEach(node => {
484
- node.isColorNode = false;
485
- node.brightness = brightness;
486
- });
487
- } else {
488
- state.mainNode.brightness = brightness;
489
- }
490
- }}
491
- onBrightnessChangeComplete={brightness => {
492
- if (state.mainBucketSelected) {
493
- state.mood.mainLamp.nodes.forEach(node => {
494
- node.isColorNode = false;
495
- node.brightness = brightness;
496
- });
497
- } else {
498
- state.mainNode.brightness = brightness;
499
- }
500
- }}
501
- />
502
- </Card>
503
- <Spacer />
504
- {(moduleParams.isFanLight || moduleParams.isUVCFan) && (
505
- <FanAdjustView
506
- fanEnable={!!state.mood.mainLamp.fanEnable}
507
- fanSpeed={state.mood.mainLamp.fanSpeed || 1}
508
- maxFanSpeed={useFanMaxSpeed()}
509
- onFanSwitch={fanEnable => {
510
- state.mood.mainLamp.fanEnable = fanEnable;
511
- }}
512
- onFanSpeedChange={fanSpeed => {
513
- state.mood.mainLamp.fanSpeed = fanSpeed;
514
- }}
515
- onFanSpeedChangeComplete={fanSpeed => {
516
- state.mood.mainLamp.fanSpeed = fanSpeed;
517
- }}
518
- style={styles.fanAdjustCard}
519
- />
520
- )}
521
- {params.mode === 'edit' && (
522
- <View style={{ marginTop: cx(20), marginHorizontal: cx(24) }}>
523
- <TextButton
524
- style={styles.deleteBtn}
525
- textStyle={styles.deleteBtnText}
526
- text={Strings.getLang('edit_static_mood_button_delete_text')}
527
- onPress={() => {
528
- showDialog({
529
- method: 'confirm',
530
- title: I18n.getLang('string_light_pp_dialog_sm_ed_headline_d'),
531
- subTitle: I18n.getLang('strip_light_static_mood_edit_dialog_text'),
532
- onConfirm: async (_, { close }) => {
533
- close()
534
- if (state.loading) return
535
- state.loading = true
536
- const res = await params.modDeleteMood('del', state.mood);
537
- state.loading = false
538
- if (res.success) {
539
- navigation.navigate(ui_biz_routerKey.ui_biz_mood);
540
- }
541
- }
542
- })
543
- }}
544
- />
545
- </View>
546
- )}
547
- <Spacer />
548
- </View>
549
- </ScrollView>
550
- </Page>
551
- );
552
- };
553
- const styles = StyleSheet.create({
554
- root: {
555
- flex: 1,
556
- flexDirection: 'column',
557
- },
558
- name: {
559
- marginHorizontal: cx(24),
560
- },
561
- adjustCard: {
562
- marginVertical: cx(12),
563
- marginHorizontal: cx(24),
564
- },
565
- fanAdjustCard: {
566
- marginHorizontal: cx(24),
567
- },
568
- lightLine: {
569
- flexDirection: 'row',
570
- marginHorizontal: cx(16),
571
- },
572
- light: {
573
- color: '#000',
574
- fontSize: cx(18),
575
- fontFamily: 'helvetica_neue_lt_std_bd',
576
- },
577
- transitionMode: {
578
- marginHorizontal: cx(16),
579
- },
580
- preview: {
581
- width: cx(20),
582
- height: cx(20),
583
- marginStart: cx(12),
584
- borderRadius: cx(4),
585
- },
586
- nodesAdjust: {
587
- flexDirection: 'row',
588
- alignItems: 'center',
589
- },
590
- adjustButtons: {
591
- width: cx(44),
592
- marginStart: cx(16),
593
- },
594
- adjustButton: {
595
- width: cx(44),
596
- height: cx(44),
597
- },
598
- nodeList: {
599
- flex: 1,
600
- marginHorizontal: cx(16),
601
- },
602
- nodeItem: {
603
- flexDirection: 'row',
604
- alignItems: 'center',
605
- },
606
- nodeBlock: {
607
- flex: 1,
608
- height: cx(40),
609
- borderRadius: cx(8),
610
- },
611
- nodeDeleteBtn: {
612
- width: cx(24),
613
- height: cx(30),
614
- justifyContent: 'center',
615
- alignItems: 'center',
616
- },
617
- nodeDeleteIcon: {
618
- width: cx(16),
619
- height: cx(16),
620
- },
621
- nodeAddBtn: {
622
- height: cx(40),
623
- justifyContent: 'center',
624
- alignItems: 'center',
625
- marginEnd: cx(26),
626
- borderRadius: cx(8),
627
- borderWidth: cx(1),
628
- borderStyle: 'dashed',
629
- borderColor: '#666',
630
- backgroundColor: '#f6f6f6',
631
- },
632
- deleteBtn: {
633
- width: '100%',
634
- height: cx(50),
635
- backgroundColor: '#666',
636
- borderRadius: cx(8),
637
- },
638
- deleteBtnText: {
639
- color: '#fff',
640
- fontSize: cx(16),
641
- fontFamily: 'helvetica_neue_lt_std_bd',
642
- },
643
- });
644
- export default DynamicMoodEditorPage;
645
- export function getTransitionModeString(transitionMode: MoodNodeTransitionMode): string {
646
- if (transitionMode === MoodNodeTransitionMode.Jump) {
647
- return Strings.getLang('other_lights_modes_jump_text');
648
- }
649
- return Strings.getLang('other_lights_modes_gradient_text');
650
- }
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
+ import {
3
+ FlatList,
4
+ Image,
5
+ ScrollView,
6
+ StyleSheet,
7
+ Text,
8
+ TouchableOpacity,
9
+ View,
10
+ } from 'react-native';
11
+ import { Utils } from 'tuya-panel-kit';
12
+ import { useReactive } from 'ahooks';
13
+ import { cloneDeep, find, isEqual } from 'lodash';
14
+ import Page from '@ledvance/base/src/components/Page';
15
+ import Strings from '@ledvance/base/src/i18n';
16
+ import { StaticMoodEditorPageParams } from './StaticMoodEditorPage';
17
+ import { useNavigation, useRoute } from '@react-navigation/native';
18
+ import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils';
19
+ import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
20
+ import res from '@ledvance/base/src/res';
21
+ import TextField from '@ledvance/base/src/components/TextField';
22
+ import Card from '@ledvance/base/src/components/Card';
23
+ import Spacer from '@ledvance/base/src/components/Spacer';
24
+ import LampAdjustView from '@ledvance/base/src/components/LampAdjustView';
25
+ import LdvSlider from '@ledvance/base/src/components/ldvSlider';
26
+ import { useFanMaxSpeed } from '@ledvance/base/src/models/modules/NativePropsSlice';
27
+ import TextButton from '@ledvance/base/src/components/TextButton';
28
+ import {
29
+ MoodNodeTransitionMode,
30
+ stripLightMoodMode,
31
+ stringLightMoodMode,
32
+ lightMoodMode,
33
+ StripLightMoodMode,
34
+ MoodUIInfo,
35
+ } from './Interface';
36
+ import TextFieldStyleButton from '@ledvance/base/src/components/TextFieldStyleButton';
37
+ import FanAdjustView from '@ledvance/base/src/components/FanAdjustView';
38
+ import { ui_biz_routerKey } from '../../navigation/Routers'
39
+ import { SelectPageParams } from '@ledvance/ui-biz-bundle/src/modules/select/SelectPage';
40
+ import I18n from '@ledvance/base/src/i18n';
41
+ import Segmented from '@ledvance/base/src/components/Segmented';
42
+ import { MoodNodeInfo } from './Interface';
43
+ import { showDialog } from '@ledvance/base/src/utils/common';
44
+
45
+ const cx = Utils.RatioUtils.convertX;
46
+ interface DynamicMoodEditorPageState {
47
+ headline: string;
48
+ mood: MoodUIInfo;
49
+ mainNode: MoodNodeInfo;
50
+ mainBucketSelected: boolean;
51
+ sceneMode: StripLightMoodMode;
52
+ loading: boolean;
53
+ }
54
+ const DynamicMoodEditorPage = () => {
55
+ const navigation = useNavigation();
56
+ const routeParams = useRoute().params as StaticMoodEditorPageParams;
57
+ const params = cloneDeep(routeParams);
58
+ const moduleParams = params.moduleParams;
59
+ const state = useReactive<DynamicMoodEditorPageState>({
60
+ headline: '',
61
+ mood: params.currentMood,
62
+ mainNode: params.currentMood.mainLamp.nodes[params.currentMood.mainLamp.nodes.length - 1],
63
+ mainBucketSelected: false,
64
+ loading: false,
65
+ sceneMode: moduleParams.isStringLight
66
+ ? stringLightMoodMode
67
+ : moduleParams.isStripLight
68
+ ? stripLightMoodMode
69
+ : lightMoodMode,
70
+ });
71
+
72
+ useEffect(() => {
73
+ state.headline = Strings.getLang(
74
+ params.mode === 'add'
75
+ ? 'add_new_dynamic_mood_headline_text'
76
+ : 'edit_static_mood_headline_text'
77
+ );
78
+ }, [params.mode]);
79
+
80
+ const getColorBlockColor = useCallback((node: MoodNodeInfo) => {
81
+ const s = Math.round(mapFloatToRange(node.s / 100, 30, 100));
82
+ if (node.isColorNode) {
83
+ return hsv2Hex(node.h, s, 100);
84
+ } else {
85
+ return cctToColor(node.colorTemp.toFixed());
86
+ }
87
+ }, []);
88
+
89
+ const getNodeColor = useCallback((node: MoodNodeInfo) => {
90
+ if (node.isColorNode) {
91
+ const s = Math.round(mapFloatToRange(node.s / 100, 30, 100));
92
+ return hsv2Hex(node.h, s, 100);
93
+ }
94
+ return cctToColor(node.colorTemp.toFixed());
95
+ }, []);
96
+
97
+ const createSelectModeData = useCallback(
98
+ (mode: number, moodMode?: StripLightMoodMode) => {
99
+ return Object.values(moodMode ? moodMode : state.sceneMode).map(scene => {
100
+ return {
101
+ text: scene.title,
102
+ selected: scene.mode === mode,
103
+ value: scene.mode,
104
+ };
105
+ });
106
+ },
107
+ [state.sceneMode]
108
+ );
109
+
110
+ const createSelectOtherData = useCallback((otherData, expand) => {
111
+ return otherData.map(other => {
112
+ return {
113
+ text: other.label,
114
+ selected: other.value === expand,
115
+ value: other.value,
116
+ };
117
+ });
118
+ }, []);
119
+
120
+ const getSelectOther = useCallback((otherData, expand) => {
121
+ const currentOther = otherData.find(other => other.value === expand);
122
+ return currentOther.label;
123
+ }, []);
124
+
125
+ const nameRepeat = useMemo(() => {
126
+ return params.nameRepeat(state.mood)
127
+ }, [state.mood.name]);
128
+
129
+ const checkMoodChanged = useMemo(() =>{
130
+ return isEqual(state.mood, params.currentMood)
131
+ }, [JSON.stringify(state.mood), params.currentMood])
132
+
133
+ const canSaveMoodData = useMemo(() =>{
134
+ return state.mood.name.length > 0 && state.mood.name.length < 33 && !nameRepeat && (params.mode === 'add' || !checkMoodChanged)
135
+ }, [nameRepeat, state.mood.name, checkMoodChanged, params.mode])
136
+
137
+ return (
138
+ <Page
139
+ backText={Strings.getLang('mesh_device_detail_mode')}
140
+ showBackDialog={!checkMoodChanged}
141
+ backDialogTitle={Strings.getLang(
142
+ params.mode === 'add'
143
+ ? 'string_light_pp_dialog_sm_add_headline_c'
144
+ : 'manage_user_unsaved_changes_dialog_headline'
145
+ )}
146
+ backDialogContent={Strings.getLang(
147
+ params.mode === 'add'
148
+ ? 'strip_light_static_mood_add_step_2_dialog_text'
149
+ : 'strip_light_static_mood_editor_step_2_dialog_text'
150
+ )}
151
+ headlineText={state.headline}
152
+ rightButtonIcon={canSaveMoodData ? res.ic_check : res.ic_uncheck}
153
+ rightButtonIconClick={async () => {
154
+ if (state.loading || !canSaveMoodData) return;
155
+ state.loading = true;
156
+ const res = await params.modDeleteMood(params.mode, cloneDeep(state.mood));
157
+ if (res.success) {
158
+ navigation.navigate(ui_biz_routerKey.ui_biz_mood);
159
+ }
160
+ state.loading = false;
161
+ }}
162
+ loading={state.loading}
163
+ >
164
+ <ScrollView style={{ flex: 1 }} nestedScrollEnabled={true}>
165
+ <View style={styles.root}>
166
+ <TextField
167
+ style={styles.name}
168
+ value={state.mood.name}
169
+ placeholder={Strings.getLang('edit_static_mood_inputfield_topic_text')}
170
+ onChangeText={text => {
171
+ state.mood.name = text;
172
+ }}
173
+ maxLength={33}
174
+ showError={state.mood.name.length > 32 || nameRepeat}
175
+ tipColor={nameRepeat ? '#f00' : undefined}
176
+ tipIcon={nameRepeat ? res.ic_text_field_input_error : undefined}
177
+ errorText={Strings.getLang(
178
+ nameRepeat ? 'string_light_pp_field_sm_add_error1' : 'add_new_dynamic_mood_alert_text'
179
+ )}
180
+ />
181
+ <Card style={styles.adjustCard}>
182
+ <Spacer height={cx(16)} />
183
+ <View style={styles.lightLine}>
184
+ <Text style={styles.light}>
185
+ {Strings.getLang('light_sources_tile_tw_lighting_headline')}
186
+ </Text>
187
+ </View>
188
+ <Spacer height={cx(18)} />
189
+ <TextFieldStyleButton
190
+ style={styles.transitionMode}
191
+ text={state.sceneMode[state.mood.mainLamp.mode]?.title}
192
+ placeholder={Strings.getLang('add_new_dynamic_mood_color_changing_mode_headline')}
193
+ onPress={() => {
194
+ const paramsSelect: SelectPageParams<number> = {
195
+ title: I18n.getLang('add_new_dynamic_mood_color_changing_mode_headline'),
196
+ data: createSelectModeData(state.mood.mainLamp.mode),
197
+ onSelect: selectPageData => {
198
+ state.mood.mainLamp.mode = selectPageData.value;
199
+ },
200
+ };
201
+ navigation.navigate(ui_biz_routerKey.ui_biz_select_page, paramsSelect);
202
+ }}
203
+ />
204
+ <Spacer height={cx(10)} />
205
+ <LdvSlider
206
+ title={Strings.getLang('add_new_dynamic_mood_lights_field_speed_topic_text')}
207
+ value={state.mood.mainLamp.speed}
208
+ onValueChange={() => {}}
209
+ onSlidingComplete={value => {
210
+ state.mood.mainLamp.speed = value;
211
+ }}
212
+ />
213
+ <Spacer height={cx(16)} />
214
+ {state.sceneMode[state.mood.mainLamp.mode]?.turnOn && (
215
+ <View style={styles.transitionMode}>
216
+ <Segmented
217
+ value={state.mood.mainLamp.direction}
218
+ options={[
219
+ {
220
+ label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text1'),
221
+ value: 0,
222
+ },
223
+ {
224
+ label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text2'),
225
+ value: 1,
226
+ },
227
+ ]}
228
+ onChange={v => (state.mood.mainLamp.direction = Number(v))}
229
+ />
230
+ <Spacer />
231
+ </View>
232
+ )}
233
+ {state.sceneMode[state.mood.mainLamp.mode]?.paragraph && (
234
+ <View style={styles.transitionMode}>
235
+ <Segmented
236
+ value={state.mood.mainLamp.segmented}
237
+ options={[
238
+ {
239
+ label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text3'),
240
+ value: 0,
241
+ },
242
+ {
243
+ label: I18n.getLang('add_new_dynamic_mood_strip_lights_switch_tab_text4'),
244
+ value: 1,
245
+ },
246
+ ]}
247
+ onChange={v => (state.mood.mainLamp.segmented = Number(v))}
248
+ />
249
+ <Spacer />
250
+ </View>
251
+ )}
252
+ {state.sceneMode[state.mood.mainLamp.mode]?.other && (
253
+ <>
254
+ <TextFieldStyleButton
255
+ style={styles.transitionMode}
256
+ text={getSelectOther(
257
+ state.sceneMode[state.mood.mainLamp.mode]?.other,
258
+ state.mood.mainLamp.expand
259
+ )}
260
+ placeholder={I18n.getLang(
261
+ 'add_new_dynamic_mood_strip_lights_selectionfield2_topic_text'
262
+ )}
263
+ onPress={() => {
264
+ const paramsSelect: SelectPageParams<number> = {
265
+ title: I18n.getLang(
266
+ 'add_new_dynamic_mood_strip_lights_selectionfield2_topic_text'
267
+ ),
268
+ data: createSelectOtherData(
269
+ state.sceneMode[state.mood.mainLamp.mode]?.other,
270
+ state.mood.mainLamp.expand
271
+ ),
272
+ onSelect: selectPageData => {
273
+ state.mood.mainLamp.expand = selectPageData.value;
274
+ },
275
+ };
276
+ navigation.navigate(ui_biz_routerKey.ui_biz_select_page, paramsSelect);
277
+ }}
278
+ />
279
+ <Spacer />
280
+ </>
281
+ )}
282
+ <View style={styles.nodesAdjust}>
283
+ <View style={styles.adjustButtons}>
284
+ <TouchableOpacity
285
+ onPress={() => {
286
+ state.mainBucketSelected = true;
287
+ }}
288
+ >
289
+ <Image
290
+ style={[
291
+ styles.adjustButton,
292
+ { tintColor: state.mainBucketSelected ? '#f60' : '#666' },
293
+ ]}
294
+ source={res.ic_paint_bucket}
295
+ />
296
+ </TouchableOpacity>
297
+ <TouchableOpacity
298
+ onPress={() => {
299
+ state.mainBucketSelected = false;
300
+ }}
301
+ >
302
+ <Image
303
+ style={[
304
+ styles.adjustButton,
305
+ { tintColor: state.mainBucketSelected ? '#666' : '#f60' },
306
+ ]}
307
+ source={res.ic_colorize}
308
+ />
309
+ </TouchableOpacity>
310
+ </View>
311
+ <FlatList
312
+ data={state.mood.mainLamp.nodes}
313
+ style={styles.nodeList}
314
+ renderItem={({ item, index }) => {
315
+ return (
316
+ <View style={styles.nodeItem}>
317
+ <TouchableOpacity
318
+ style={[
319
+ styles.nodeBlock,
320
+ {
321
+ backgroundColor: getNodeColor(item),
322
+ },
323
+ ]}
324
+ onPress={() => {
325
+ state.mainNode = item;
326
+ }}
327
+ />
328
+ <TouchableOpacity
329
+ style={styles.nodeDeleteBtn}
330
+ disabled={state.mood.mainLamp.nodes.length < 3}
331
+ onPress={() => {
332
+ state.mood.mainLamp.nodes.splice(index, 1);
333
+ state.mainNode = state.mood.mainLamp.nodes[state.mood.mainLamp.nodes.length - 1];
334
+ }}
335
+ >
336
+ <Image
337
+ style={[
338
+ styles.nodeDeleteIcon,
339
+ {
340
+ tintColor: state.mood.mainLamp.nodes.length < 3 ? '#ccc' : '#666',
341
+ },
342
+ ]}
343
+ source={res.ic_mood_del}
344
+ />
345
+ </TouchableOpacity>
346
+ </View>
347
+ );
348
+ }}
349
+ keyExtractor={(_, index) => `${index}`}
350
+ ItemSeparatorComponent={() => <Spacer height={cx(12)} />}
351
+ ListFooterComponent={() => {
352
+ if (state.mood.mainLamp.nodes.length >= 8) {
353
+ return <></>;
354
+ }
355
+ return (
356
+ <View>
357
+ <Spacer height={cx(12)} />
358
+ <TouchableOpacity
359
+ style={styles.nodeAddBtn}
360
+ onPress={() => {
361
+ const node = {
362
+ ...state.mainNode,
363
+ };
364
+ state.mood.mainLamp.nodes.push(node);
365
+ state.mainNode = node;
366
+ }}
367
+ >
368
+ <Image
369
+ style={{
370
+ width: cx(18),
371
+ height: cx(18),
372
+ tintColor: '#000',
373
+ }}
374
+ source={{ uri: res.add }}
375
+ />
376
+ </TouchableOpacity>
377
+ </View>
378
+ );
379
+ }}
380
+ />
381
+ </View>
382
+ <Spacer />
383
+ <View style={styles.lightLine}>
384
+ <Text style={styles.light}>
385
+ {Strings.getLang('add_new_dynamic_mood_lights_field_headline2_text')}
386
+ </Text>
387
+ <View
388
+ style={[styles.preview, { backgroundColor: getColorBlockColor(state.mainNode) }]}
389
+ />
390
+ </View>
391
+ <Spacer />
392
+ <LampAdjustView
393
+ isSupportColor={moduleParams.isSupportColor}
394
+ isSupportBrightness={moduleParams.isSupportBrightness}
395
+ isSupportTemperature={moduleParams.isSupportTemperature}
396
+ isColorMode={state.mainNode.isColorNode}
397
+ reserveSV={true}
398
+ setIsColorMode={isColorMode => {
399
+ if (state.mainBucketSelected) {
400
+ state.mood.mainLamp.nodes.forEach(node => {
401
+ node.isColorNode = isColorMode;
402
+ if (isColorMode && node.h === 0 && node.s === 0 && node.v === 0) {
403
+ node.s = 100;
404
+ node.v = 100;
405
+ } else {
406
+ if (node.brightness === 0 && node.colorTemp === 0) {
407
+ node.brightness = 100;
408
+ }
409
+ }
410
+ });
411
+ } else {
412
+ state.mainNode.isColorNode = isColorMode;
413
+ if (
414
+ isColorMode &&
415
+ state.mainNode.h === 0 &&
416
+ state.mainNode.s === 0 &&
417
+ state.mainNode.v === 0
418
+ ) {
419
+ state.mainNode.s = 100;
420
+ state.mainNode.v = 100;
421
+ } else {
422
+ if (state.mainNode.brightness === 0 && state.mainNode.colorTemp === 0) {
423
+ state.mainNode.brightness = 100;
424
+ }
425
+ }
426
+ }
427
+ }}
428
+ h={state.mainNode.h}
429
+ s={state.mainNode.s}
430
+ v={state.mainNode.v}
431
+ onHSVChange={(h, s, v) => {
432
+ if (state.mainBucketSelected) {
433
+ state.mood.mainLamp.nodes.forEach(node => {
434
+ node.isColorNode = true;
435
+ node.h = h;
436
+ node.s = s;
437
+ node.v = v;
438
+ });
439
+ } else {
440
+ state.mainNode.h = h;
441
+ state.mainNode.s = s;
442
+ state.mainNode.v = v;
443
+ }
444
+ }}
445
+ onHSVChangeComplete={(h, s, v) => {
446
+ if (state.mainBucketSelected) {
447
+ state.mood.mainLamp.nodes.forEach(node => {
448
+ node.isColorNode = true;
449
+ node.h = h;
450
+ node.s = s;
451
+ node.v = v;
452
+ });
453
+ } else {
454
+ state.mainNode.h = h;
455
+ state.mainNode.s = s || 1;
456
+ state.mainNode.v = v;
457
+ }
458
+ }}
459
+ colorTemp={state.mainNode.colorTemp}
460
+ brightness={state.mainNode.brightness}
461
+ onCCTChange={cct => {
462
+ if (state.mainBucketSelected) {
463
+ state.mood.mainLamp.nodes.forEach(node => {
464
+ node.isColorNode = false;
465
+ node.colorTemp = cct;
466
+ });
467
+ } else {
468
+ state.mainNode.colorTemp = cct;
469
+ }
470
+ }}
471
+ onCCTChangeComplete={cct => {
472
+ if (state.mainBucketSelected) {
473
+ state.mood.mainLamp.nodes.forEach(node => {
474
+ node.isColorNode = false;
475
+ node.colorTemp = cct;
476
+ });
477
+ } else {
478
+ state.mainNode.colorTemp = cct;
479
+ }
480
+ }}
481
+ onBrightnessChange={brightness => {
482
+ if (state.mainBucketSelected) {
483
+ state.mood.mainLamp.nodes.forEach(node => {
484
+ node.isColorNode = false;
485
+ node.brightness = brightness;
486
+ });
487
+ } else {
488
+ state.mainNode.brightness = brightness;
489
+ }
490
+ }}
491
+ onBrightnessChangeComplete={brightness => {
492
+ if (state.mainBucketSelected) {
493
+ state.mood.mainLamp.nodes.forEach(node => {
494
+ node.isColorNode = false;
495
+ node.brightness = brightness;
496
+ });
497
+ } else {
498
+ state.mainNode.brightness = brightness;
499
+ }
500
+ }}
501
+ />
502
+ </Card>
503
+ <Spacer />
504
+ {(moduleParams.isFanLight || moduleParams.isUVCFan) && (
505
+ <FanAdjustView
506
+ fanEnable={!!state.mood.mainLamp.fanEnable}
507
+ fanSpeed={state.mood.mainLamp.fanSpeed || 1}
508
+ maxFanSpeed={useFanMaxSpeed()}
509
+ onFanSwitch={fanEnable => {
510
+ state.mood.mainLamp.fanEnable = fanEnable;
511
+ }}
512
+ onFanSpeedChange={fanSpeed => {
513
+ state.mood.mainLamp.fanSpeed = fanSpeed;
514
+ }}
515
+ onFanSpeedChangeComplete={fanSpeed => {
516
+ state.mood.mainLamp.fanSpeed = fanSpeed;
517
+ }}
518
+ style={styles.fanAdjustCard}
519
+ />
520
+ )}
521
+ {params.mode === 'edit' && (
522
+ <View style={{ marginTop: cx(20), marginHorizontal: cx(24) }}>
523
+ <TextButton
524
+ style={styles.deleteBtn}
525
+ textStyle={styles.deleteBtnText}
526
+ text={Strings.getLang('edit_static_mood_button_delete_text')}
527
+ onPress={() => {
528
+ showDialog({
529
+ method: 'confirm',
530
+ title: I18n.getLang('string_light_pp_dialog_sm_ed_headline_d'),
531
+ subTitle: I18n.getLang('strip_light_static_mood_edit_dialog_text'),
532
+ onConfirm: async (_, { close }) => {
533
+ close()
534
+ if (state.loading) return
535
+ state.loading = true
536
+ const res = await params.modDeleteMood('del', state.mood);
537
+ state.loading = false
538
+ if (res.success) {
539
+ navigation.navigate(ui_biz_routerKey.ui_biz_mood);
540
+ }
541
+ }
542
+ })
543
+ }}
544
+ />
545
+ </View>
546
+ )}
547
+ <Spacer />
548
+ </View>
549
+ </ScrollView>
550
+ </Page>
551
+ );
552
+ };
553
+ const styles = StyleSheet.create({
554
+ root: {
555
+ flex: 1,
556
+ flexDirection: 'column',
557
+ },
558
+ name: {
559
+ marginHorizontal: cx(24),
560
+ },
561
+ adjustCard: {
562
+ marginVertical: cx(12),
563
+ marginHorizontal: cx(24),
564
+ },
565
+ fanAdjustCard: {
566
+ marginHorizontal: cx(24),
567
+ },
568
+ lightLine: {
569
+ flexDirection: 'row',
570
+ marginHorizontal: cx(16),
571
+ },
572
+ light: {
573
+ color: '#000',
574
+ fontSize: cx(18),
575
+ fontFamily: 'helvetica_neue_lt_std_bd',
576
+ },
577
+ transitionMode: {
578
+ marginHorizontal: cx(16),
579
+ },
580
+ preview: {
581
+ width: cx(20),
582
+ height: cx(20),
583
+ marginStart: cx(12),
584
+ borderRadius: cx(4),
585
+ },
586
+ nodesAdjust: {
587
+ flexDirection: 'row',
588
+ alignItems: 'center',
589
+ },
590
+ adjustButtons: {
591
+ width: cx(44),
592
+ marginStart: cx(16),
593
+ },
594
+ adjustButton: {
595
+ width: cx(44),
596
+ height: cx(44),
597
+ },
598
+ nodeList: {
599
+ flex: 1,
600
+ marginHorizontal: cx(16),
601
+ },
602
+ nodeItem: {
603
+ flexDirection: 'row',
604
+ alignItems: 'center',
605
+ },
606
+ nodeBlock: {
607
+ flex: 1,
608
+ height: cx(40),
609
+ borderRadius: cx(8),
610
+ },
611
+ nodeDeleteBtn: {
612
+ width: cx(24),
613
+ height: cx(30),
614
+ justifyContent: 'center',
615
+ alignItems: 'center',
616
+ },
617
+ nodeDeleteIcon: {
618
+ width: cx(16),
619
+ height: cx(16),
620
+ },
621
+ nodeAddBtn: {
622
+ height: cx(40),
623
+ justifyContent: 'center',
624
+ alignItems: 'center',
625
+ marginEnd: cx(26),
626
+ borderRadius: cx(8),
627
+ borderWidth: cx(1),
628
+ borderStyle: 'dashed',
629
+ borderColor: '#666',
630
+ backgroundColor: '#f6f6f6',
631
+ },
632
+ deleteBtn: {
633
+ width: '100%',
634
+ height: cx(50),
635
+ backgroundColor: '#666',
636
+ borderRadius: cx(8),
637
+ },
638
+ deleteBtnText: {
639
+ color: '#fff',
640
+ fontSize: cx(16),
641
+ fontFamily: 'helvetica_neue_lt_std_bd',
642
+ },
643
+ });
644
+ export default DynamicMoodEditorPage;
645
+ export function getTransitionModeString(transitionMode: MoodNodeTransitionMode): string {
646
+ if (transitionMode === MoodNodeTransitionMode.Jump) {
647
+ return Strings.getLang('other_lights_modes_jump_text');
648
+ }
649
+ return Strings.getLang('other_lights_modes_gradient_text');
650
+ }