@ledvance/ui-biz-bundle 1.1.146 → 1.1.147

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.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "@ledvance/ui-biz-bundle",
5
5
  "pid": [],
6
6
  "uiid": "",
7
- "version": "1.1.146",
7
+ "version": "1.1.147",
8
8
  "scripts": {},
9
9
  "dependencies": {
10
10
  "@ledvance/base": "^1.x",
@@ -1,10 +1,11 @@
1
- import { StyleSheet, View, Text, ViewStyle, Image } from 'react-native'
1
+ import { StyleSheet, View, Text, ViewStyle, Image, TouchableOpacity } from 'react-native'
2
2
  import React from 'react'
3
3
  import Card from '@ledvance/base/src/components/Card'
4
- import { SwitchButton, Utils } from 'tuya-panel-kit'
4
+ import { Utils } from 'tuya-panel-kit'
5
5
  import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine'
6
6
  import Spacer from '@ledvance/base/src/components/Spacer'
7
7
  import ThemeType from '@ledvance/base/src/config/themeType'
8
+ import res from '@ledvance/base/src/res'
8
9
 
9
10
  const cx = Utils.RatioUtils.convertX
10
11
  const { withTheme } = Utils.ThemeUtils
@@ -31,6 +32,7 @@ function FlagItem(props: RecommendMoodItemProps) {
31
32
  flexDirection: 'row',
32
33
  marginHorizontal: cx(16),
33
34
  justifyContent: 'space-between',
35
+ alignItems: 'center',
34
36
  },
35
37
  headText: {
36
38
  color: props.theme?.global.fontColor,
@@ -39,6 +41,13 @@ function FlagItem(props: RecommendMoodItemProps) {
39
41
  lineHeight: cx(20),
40
42
  flex: 1
41
43
  },
44
+ checkbox: {
45
+ width: cx(45),
46
+ height: cx(45),
47
+ marginTop: cx(-5),
48
+ marginBottom: cx(-10),
49
+ fontWeight: 'bold',
50
+ },
42
51
  gradientItem: {
43
52
  alignItems: 'center',
44
53
  },
@@ -55,10 +64,9 @@ function FlagItem(props: RecommendMoodItemProps) {
55
64
  {props.title ? <Text style={styles.headText}>{props.title}</Text> : undefined}
56
65
  {props.icon ? <Image source={icon} style={{ width: cx(60), aspectRatio: 2.14, marginRight: cx(10) }} /> : undefined}
57
66
  </View>
58
- <SwitchButton
59
- thumbStyle={{ elevation: 0 }}
60
- value={props.enable}
61
- onValueChange={props.onSwitch} />
67
+ <TouchableOpacity style={styles.checkbox} onPress={() => props.onSwitch(!props.enable)}>
68
+ <Image source={{ uri: res.ic_check}} width={cx(44)} height={cx(44)} style={[styles.checkbox, { tintColor: props.enable ? props.theme?.icon.primary : props.theme?.icon.disable}]} />
69
+ </TouchableOpacity>
62
70
  </View>
63
71
  <Spacer />
64
72
  <View style={styles.gradientItem}>
@@ -149,31 +149,94 @@ export interface PowerDataItem {
149
149
  value: number
150
150
  }
151
151
 
152
- export async function getPowerData(devId: string, powerDpCode: string, interval: number): Promise<PowerDataItem[]> {
153
- const now = dayjs()
154
- const startTime = now.add(-1 * interval, 'minute').valueOf().toString()
155
- const endTime = now.valueOf().toString()
156
- const dpResult = await getSpecifiedTimeDpReportLogs(
157
- devId,
158
- [powerDpCode],
159
- 'ASC',
160
- startTime,
161
- endTime,
162
- {
163
- maxRetries: 5,
164
- initialDelay: 1000,
165
- maxDelay: 30000,
166
- backoffFactor: 2
167
- }
168
- ) as DpReportSataData[]
169
- return dpResult.map(dp => {
170
- return {
171
- key: dp.timeStr,
172
- chartTitle: dayjs.unix(dp.timeStamp).format('MM/DD/YYYY HH:mm:ss'),
173
- time: dp.timeStamp * 1000,
174
- value: parseFloat(dp.value) / 10
152
+ // 常量抽离(便于维护)
153
+ const DATA_POINT_INTERVAL_SEC = 10; // 预设数据间隔(秒)
154
+ const TIME_UNIT = 'minute' as const; // 时间单位(与 interval 配合)
155
+ const RETRY_CONFIG = {
156
+ maxRetries: 5,
157
+ initialDelay: 1000,
158
+ maxDelay: 30000,
159
+ backoffFactor: 2,
160
+ } as const;
161
+
162
+ /**
163
+ * 获取设备功率数据(保留所有实际数据,补充10秒间隔预设数据)
164
+ * @param devId 设备ID
165
+ * @param powerDpCode 功率数据点编码
166
+ * @param interval 时间区间(分钟)
167
+ * @returns 按时间排序的完整数据(预设数据 + 所有实际数据)
168
+ */
169
+ export async function getPowerData(
170
+ devId: string,
171
+ powerDpCode: string,
172
+ interval: number
173
+ ): Promise<PowerDataItem[]> {
174
+ try {
175
+ const now = dayjs();
176
+ const startTime = now.add(-interval, TIME_UNIT); // 起始时间:当前时间 - interval 分钟
177
+ const endTime = now; // 结束时间:当前时间
178
+ const startTimeMs = startTime.valueOf();
179
+ const endTimeMs = endTime.valueOf();
180
+
181
+ // 1. 生成预设数据(10秒间隔,仅覆盖 [startTime, endTime] 区间)
182
+ const totalSeconds = interval * 60;
183
+ const expectedDataPoints = Math.floor(totalSeconds / DATA_POINT_INTERVAL_SEC);
184
+ const presetData: PowerDataItem[] = [];
185
+ for (let i = 0; i < expectedDataPoints; i++) {
186
+ const timePoint = startTime.add(i * DATA_POINT_INTERVAL_SEC, 'second');
187
+ const timeMs = timePoint.valueOf();
188
+ // 确保预设数据不超出查询区间(防止边界计算误差)
189
+ if (timeMs > endTimeMs) break;
190
+ presetData.push({
191
+ key: timePoint.format('HH:mm:ss'),
192
+ chartTitle: timePoint.format('MM/DD/YYYY HH:mm:ss'),
193
+ time: timeMs,
194
+ value: 0,
195
+ });
175
196
  }
176
- })
197
+
198
+ // 2. 请求并处理实际数据(保留所有实际数据,不过滤)
199
+ const dpResult = await getSpecifiedTimeDpReportLogs(
200
+ devId,
201
+ [powerDpCode],
202
+ 'ASC',
203
+ startTimeMs.toString(),
204
+ endTimeMs.toString(),
205
+ RETRY_CONFIG
206
+ );
207
+ // 类型校验 + 格式转换(避免异常)
208
+ const validDpResult = Array.isArray(dpResult) ? (dpResult as DpReportSataData[]) : [];
209
+ const actualData: PowerDataItem[] = validDpResult
210
+ .map((dp) => {
211
+ const timeMs = dp.timeStamp * 1000; // 秒级转毫秒级
212
+ // 过滤超出查询区间的实际数据(可选,根据业务需求)
213
+ if (timeMs < startTimeMs || timeMs > endTimeMs) return null;
214
+ return {
215
+ key: dayjs.unix(dp.timeStamp).format('HH:mm:ss'), // 统一 key 格式
216
+ chartTitle: dayjs.unix(dp.timeStamp).format('MM/DD/YYYY HH:mm:ss'),
217
+ time: timeMs,
218
+ value: parseFloat(dp.value) / 10, // 业务数值转换
219
+ };
220
+ })
221
+ .filter((item): item is PowerDataItem => item !== null); // 过滤无效数据
222
+
223
+ // 3. 合并预设数据和实际数据(去重:实际数据优先覆盖预设数据)
224
+ // 用 Map 实现 O(n) 合并(key:时间戳,value:数据项,实际数据覆盖预设)
225
+ const mergedMap = new Map<number, PowerDataItem>();
226
+ // 先加预设数据(后续实际数据相同时间戳会覆盖)
227
+ presetData.forEach((item) => mergedMap.set(item.time, item));
228
+ // 再加实际数据(覆盖相同时间戳的预设数据)
229
+ actualData.forEach((item) => mergedMap.set(item.time, item));
230
+
231
+ // 4. 按时间戳排序(Map 迭代顺序是插入顺序,必须显式排序)
232
+ const result = Array.from(mergedMap.values()).sort((a, b) => a.time - b.time);
233
+
234
+ return result;
235
+ } catch (error) {
236
+ console.error(`[getPowerData] 失败:devId=${devId}, powerDpCode=${powerDpCode}`, error);
237
+ // 兜底返回空数组(或仅返回预设数据,根据业务容错需求调整)
238
+ return [];
239
+ }
177
240
  }
178
241
 
179
242
  export const exportEnergyCsv = (values: any[][], unit: string) => {
@@ -342,19 +342,6 @@ const EnergyConsumptionPage = (props: {theme?: ThemeType}) => {
342
342
  <Text style={{ fontSize: cx(14), color: props.theme?.global.fontColor, }}>{I18n.getLang(isGeneration ? 'generation_data_description_text' : 'consumption_data_description_text')}</Text>
343
343
  </View>
344
344
  <Spacer />
345
- {/* Today */}
346
- <Card
347
- style={styles.cardContainer}
348
- >
349
- <Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field1_headline_text')}</Text>
350
- <Spacer height={cx(15)} />
351
- <View style={{ flexDirection: 'row', alignItems: 'flex-end' }}>
352
- <Text style={[styles.consumptionNum, { fontSize: cx(38), marginRight: cx(8) }]}>{localeNumber(state.todayElectricity)}</Text>
353
- <Text style={[styles.consumptionNum, { fontSize: cx(22), marginBottom: cx(4) }]}>kWh</Text>
354
- </View>
355
- <Spacer height={cx(10)} />
356
- </Card>
357
- <Spacer />
358
345
  <Card
359
346
  style={styles.cardContainer}
360
347
  onPress={() => {
@@ -373,7 +360,17 @@ const EnergyConsumptionPage = (props: {theme?: ThemeType}) => {
373
360
  } as EnergyConsumptionChartProps)
374
361
  }}
375
362
  >
376
- <Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field2_headline_text')}</Text>
363
+ <View style={{
364
+ flexDirection: 'row',
365
+ justifyContent: 'space-between',
366
+ alignItems: 'center',
367
+ }}>
368
+ <Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field2_headline_text')}</Text>
369
+ <Image
370
+ source={{ uri: res.energy_consumption_chart}}
371
+ style={{ width: cx(16), height: cx(16), marginLeft: cx(8) }}
372
+ />
373
+ </View>
377
374
  <Spacer height={cx(15)} />
378
375
  <View style={styles.consumedEnergyContent}>
379
376
  <ConsumedEnergyItem
@@ -393,24 +390,32 @@ const EnergyConsumptionPage = (props: {theme?: ThemeType}) => {
393
390
  style={styles.cardContainer}
394
391
  >
395
392
  <View style={{ flexDirection: 'row', alignItems: 'center' }}>
396
- <Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field3_headline_text')}</Text>
397
- { state.energyGeneration.history.length > 0 && <TouchableOpacity
398
- onPress={() => {
399
- state.showPopup = true
400
- state.popupType = 'history'
401
- }}
402
- >
403
- <Image
404
- source={{uri: res.co2_icon}}
405
- resizeMode="contain"
406
- style={{ height: cx(20), width: cx(20), tintColor: props.theme?.button.primary }}
407
- />
408
- </TouchableOpacity>}
393
+ <Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field1_headline_text')}</Text>
394
+ <View style={{ flexDirection: 'row', alignItems: 'flex-end', marginLeft: cx(10) }}>
395
+ <Text style={[styles.consumptionNum, { fontSize: cx(30), marginRight: cx(8) }]}>{localeNumber(state.todayElectricity)}</Text>
396
+ <Text style={[styles.consumptionNum, { fontSize: cx(20), marginBottom: cx(4) }]}>kWh</Text>
397
+ </View>
409
398
  </View>
410
- <Spacer height={cx(15)} />
411
- <View style={{ flexDirection: 'row', alignItems: 'flex-end' }}>
412
- <Text style={[styles.consumptionNum, { fontSize: cx(38), marginRight: cx(8) }]}>{localeNumber(totalElectricity)}</Text>
413
- <Text style={[styles.consumptionNum, { fontSize: cx(22), marginBottom: cx(4) }]}>kWh</Text>
399
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
400
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
401
+ <Text style={styles.cardTitle}>{I18n.getLang('consumption_data_field3_headline_text')}</Text>
402
+ { state.energyGeneration.history.length > 0 && <TouchableOpacity
403
+ onPress={() => {
404
+ state.showPopup = true
405
+ state.popupType = 'history'
406
+ }}
407
+ >
408
+ <Image
409
+ source={{uri: res.co2_icon}}
410
+ resizeMode="contain"
411
+ style={{ height: cx(20), width: cx(20), tintColor: props.theme?.button.primary }}
412
+ />
413
+ </TouchableOpacity>}
414
+ </View>
415
+ <View style={{ flexDirection: 'row', alignItems: 'flex-end', marginLeft: cx(10) }}>
416
+ <Text style={[styles.consumptionNum, { fontSize: cx(30), marginRight: cx(8) }]}>{localeNumber(totalElectricity)}</Text>
417
+ <Text style={[styles.consumptionNum, { fontSize: cx(20), marginBottom: cx(4) }]}>kWh</Text>
418
+ </View>
414
419
  </View>
415
420
  <Spacer />
416
421
  {/* CO2 */}
@@ -1,166 +1,172 @@
1
- import React, { useMemo } from 'react';
2
- import { StyleSheet, Text, View, ViewProps, ViewStyle, Image } from 'react-native';
3
- import { SwitchButton, Utils } from 'tuya-panel-kit';
4
- import { hsv2Hex } from '@ledvance/base/src/utils';
5
- import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
6
- import { mapFloatToRange } from '@ledvance/base/src/utils';
7
- import Card from '@ledvance/base/src/components/Card';
8
- import Spacer from '@ledvance/base/src/components/Spacer';
9
- import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine';
10
- import { MoodJumpGradientMode, MoodLampInfo, MoodUIInfo } from './Interface';
11
- import I18n from '@ledvance/base/src/i18n';
12
- import res from '@ledvance/base/src/res';
13
- import ThemeType from '@ledvance/base/src/config/themeType'
14
-
15
- const cx = Utils.RatioUtils.convertX;
16
- const { withTheme } = Utils.ThemeUtils
17
-
18
- interface LightCategory {
19
- isMixLight?: boolean
20
- isStripLight?: boolean
21
- isStringLight?: boolean
22
- isCeilingLight?: boolean
23
- }
24
-
25
- interface MoodItemProps extends ViewProps {
26
- theme?: ThemeType
27
- enable: boolean;
28
- isMix: boolean;
29
- mood: MoodUIInfo;
30
- style?: ViewStyle;
31
- deviceTypeOption?: LightCategory
32
- onPress?: () => void;
33
- onSwitch: (enable: boolean) => void;
34
- }
35
-
36
- const MoodItem = (props: MoodItemProps) => {
37
- const { mood, isMix, deviceTypeOption } = props;
38
- const isDynamic = useMemo(() => {
39
- return mood.mainLamp.nodes?.length > 1 || mood.secondaryLamp.nodes?.length > 1;
40
- }, [mood.mainLamp.nodes, mood.secondaryLamp.nodes]);
41
-
42
- const gradientMode = useMemo(() => (
43
- deviceTypeOption?.isStringLight ? MoodJumpGradientMode.StringGradient : deviceTypeOption?.isStripLight ? MoodJumpGradientMode.StripGradient : MoodJumpGradientMode.SourceGradient
44
- ), [MoodJumpGradientMode, deviceTypeOption])
45
-
46
-
47
- const styles = StyleSheet.create({
48
- card: {
49
- marginHorizontal: cx(24),
50
- },
51
- headline: {
52
- flexDirection: 'row',
53
- marginHorizontal: cx(16),
54
- },
55
- headText: {
56
- flex: 1,
57
- color: props.theme?.global.fontColor,
58
- fontSize: cx(16),
59
- fontFamily: 'helvetica_neue_lt_std_bd',
60
- lineHeight: cx(20),
61
- },
62
- moodTypeItem: {
63
- flexDirection: 'row',
64
- },
65
- moodTypeLabel: {
66
- marginStart: cx(16),
67
- paddingHorizontal: cx(12.5),
68
- backgroundColor: props.theme?.tag.background,
69
- borderRadius: cx(8),
70
- },
71
- moodTypeLabelText: {
72
- height: cx(16),
73
- color: props.theme?.tag.fontColor,
74
- fontSize: cx(10),
75
- textAlignVertical: 'center',
76
- fontFamily: 'helvetica_neue_lt_std_roman',
77
- lineHeight: cx(16),
78
- },
79
- })
80
-
81
- return (
82
- <Card style={[styles.card, props.style]} onPress={props.onPress}>
83
- <View>
84
- <Spacer height={cx(16)} />
85
- <View style={styles.headline}>
86
- <Text style={styles.headText}>{mood.name}</Text>
87
- <SwitchButton
88
- thumbStyle={{ elevation: 0 }}
89
- value={props.enable}
90
- onValueChange={props.onSwitch}
91
- />
92
- </View>
93
- <Spacer />
94
- <MixMoodColorsLine mixSubLight={mood.mainLamp} isMix={isMix} type={(mood.mainLamp.mode === gradientMode && !deviceTypeOption?.isCeilingLight) ? 'gradient' : 'separate'}/>
95
- {(deviceTypeOption?.isMixLight || (isMix && !!mood.secondaryLamp.nodes.length)) && (
96
- <>
97
- <Spacer height={cx(7)} />
98
- <MixMoodColorsLine mixSubLight={mood.secondaryLamp} isMix={isMix} type={mood.secondaryLamp.mode === (deviceTypeOption?.isMixLight ? MoodJumpGradientMode.SourceGradient : MoodJumpGradientMode.StripGradient) ? 'gradient' : 'separate'}/>
99
- </>
100
- )}
101
- <Spacer height={cx(12)} />
102
- <View style={styles.moodTypeItem}>
103
- <View style={styles.moodTypeLabel}>
104
- <Text style={styles.moodTypeLabelText}>
105
- {I18n.getLang(
106
- isDynamic ? 'mood_overview_field_chip_2' : 'mood_overview_field_chip_text'
107
- )}
108
- </Text>
109
- </View>
110
- </View>
111
- <Spacer height={cx(16)} />
112
- </View>
113
- </Card>
114
- );
115
- };
116
-
117
- export default withTheme(MoodItem)
118
-
119
- export function MixMoodColorsLine(props: { mixSubLight: MoodLampInfo; isMix: boolean, type: 'gradient' | 'separate' }) {
120
- const { mixSubLight, isMix } = props;
121
- const lightColors = !!(mixSubLight.enable && mixSubLight.nodes.length) ? mixSubLight.nodes?.map(n => {
122
- const s = Math.round(mapFloatToRange(n.s / 100, 30, 100));
123
- return n.isColorNode
124
- ? hsv2Hex(n.h, s, Math.round(mapFloatToRange(n.v / 100, 50, 100)))
125
- : cctToColor(n.colorTemp.toFixed());
126
- }) : ['#eee'];
127
-
128
- const styles = StyleSheet.create({
129
- gradientItem: {
130
- flexDirection: 'row',
131
- },
132
- gradientItemIconView: {
133
- width: cx(24),
134
- height: cx(24),
135
- justifyContent: 'center',
136
- alignItems: 'center',
137
- backgroundColor: '#aaa',
138
- borderRadius: cx(8),
139
- },
140
- gradientItemIcon: {
141
- width: cx(16),
142
- height: cx(16),
143
- tintColor: '#fff',
144
- },
145
- })
146
-
147
- return (
148
- <View style={styles.gradientItem}>
149
- <Spacer height={0} width={cx(16)} />
150
- <MoodColorsLine
151
- nodeStyle={{ borderColor: '#ccc', borderWidth: 1 }}
152
- width={isMix ? cx(264) : undefined}
153
- type={props.type}
154
- colors={lightColors}
155
- />
156
- {isMix && (
157
- <>
158
- <Spacer height={0} width={cx(7)} />
159
- <View style={styles.gradientItemIconView}>
160
- <Image style={styles.gradientItemIcon} source={{uri: mixSubLight.enable ? res.light_on : res.light_off}} />
161
- </View>
162
- </>
163
- )}
164
- </View>
165
- );
166
- }
1
+ import React, { useMemo } from 'react';
2
+ import { StyleSheet, Text, View, ViewProps, ViewStyle, Image, TouchableOpacity } from 'react-native';
3
+ import { Utils } from 'tuya-panel-kit';
4
+ import { hsv2Hex } from '@ledvance/base/src/utils';
5
+ import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
6
+ import { mapFloatToRange } from '@ledvance/base/src/utils';
7
+ import Card from '@ledvance/base/src/components/Card';
8
+ import Spacer from '@ledvance/base/src/components/Spacer';
9
+ import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine';
10
+ import { MoodJumpGradientMode, MoodLampInfo, MoodUIInfo } from './Interface';
11
+ import I18n from '@ledvance/base/src/i18n';
12
+ import res from '@ledvance/base/src/res';
13
+ import ThemeType from '@ledvance/base/src/config/themeType'
14
+
15
+ const cx = Utils.RatioUtils.convertX;
16
+ const { withTheme } = Utils.ThemeUtils
17
+
18
+ interface LightCategory {
19
+ isMixLight?: boolean
20
+ isStripLight?: boolean
21
+ isStringLight?: boolean
22
+ isCeilingLight?: boolean
23
+ }
24
+
25
+ interface MoodItemProps extends ViewProps {
26
+ theme?: ThemeType
27
+ enable: boolean;
28
+ isMix: boolean;
29
+ mood: MoodUIInfo;
30
+ style?: ViewStyle;
31
+ deviceTypeOption?: LightCategory
32
+ onPress?: () => void;
33
+ onSwitch: (enable: boolean) => void;
34
+ }
35
+
36
+ const MoodItem = (props: MoodItemProps) => {
37
+ const { mood, isMix, deviceTypeOption } = props;
38
+ const isDynamic = useMemo(() => {
39
+ return mood.mainLamp.nodes?.length > 1 || mood.secondaryLamp.nodes?.length > 1;
40
+ }, [mood.mainLamp.nodes, mood.secondaryLamp.nodes]);
41
+
42
+ const gradientMode = useMemo(() => (
43
+ deviceTypeOption?.isStringLight ? MoodJumpGradientMode.StringGradient : deviceTypeOption?.isStripLight ? MoodJumpGradientMode.StripGradient : MoodJumpGradientMode.SourceGradient
44
+ ), [MoodJumpGradientMode, deviceTypeOption])
45
+
46
+
47
+ const styles = StyleSheet.create({
48
+ card: {
49
+ marginHorizontal: cx(24),
50
+ },
51
+ headline: {
52
+ flexDirection: 'row',
53
+ marginHorizontal: cx(16),
54
+ alignItems: 'center',
55
+ },
56
+ headText: {
57
+ flex: 1,
58
+ color: props.theme?.global.fontColor,
59
+ fontSize: cx(16),
60
+ fontFamily: 'helvetica_neue_lt_std_bd',
61
+ lineHeight: cx(20),
62
+ },
63
+ checkbox: {
64
+ width: cx(45),
65
+ height: cx(45),
66
+ marginTop: cx(-5),
67
+ marginBottom: cx(-10),
68
+ fontWeight: 'bold',
69
+ },
70
+ moodTypeItem: {
71
+ flexDirection: 'row',
72
+ },
73
+ moodTypeLabel: {
74
+ marginStart: cx(16),
75
+ paddingHorizontal: cx(12.5),
76
+ backgroundColor: props.theme?.tag.background,
77
+ borderRadius: cx(8),
78
+ },
79
+ moodTypeLabelText: {
80
+ height: cx(16),
81
+ color: props.theme?.tag.fontColor,
82
+ fontSize: cx(10),
83
+ textAlignVertical: 'center',
84
+ fontFamily: 'helvetica_neue_lt_std_roman',
85
+ lineHeight: cx(16),
86
+ },
87
+ })
88
+
89
+ return (
90
+ <Card style={[styles.card, props.style]} onPress={props.onPress}>
91
+ <View>
92
+ <Spacer height={cx(16)} />
93
+ <View style={styles.headline}>
94
+ <Text style={styles.headText}>{mood.name}</Text>
95
+ <TouchableOpacity style={styles.checkbox} onPress={() => props.onSwitch(!props.enable)}>
96
+ <Image source={{ uri: res.ic_check}} width={cx(44)} height={cx(44)} style={[styles.checkbox, { tintColor: props.enable ? props.theme?.icon.primary : props.theme?.icon.disable}]} />
97
+ </TouchableOpacity>
98
+ </View>
99
+ <Spacer />
100
+ <MixMoodColorsLine mixSubLight={mood.mainLamp} isMix={isMix} type={(mood.mainLamp.mode === gradientMode && !deviceTypeOption?.isCeilingLight) ? 'gradient' : 'separate'}/>
101
+ {(deviceTypeOption?.isMixLight || (isMix && !!mood.secondaryLamp.nodes.length)) && (
102
+ <>
103
+ <Spacer height={cx(7)} />
104
+ <MixMoodColorsLine mixSubLight={mood.secondaryLamp} isMix={isMix} type={mood.secondaryLamp.mode === (deviceTypeOption?.isMixLight ? MoodJumpGradientMode.SourceGradient : MoodJumpGradientMode.StripGradient) ? 'gradient' : 'separate'}/>
105
+ </>
106
+ )}
107
+ <Spacer height={cx(12)} />
108
+ <View style={styles.moodTypeItem}>
109
+ <View style={styles.moodTypeLabel}>
110
+ <Text style={styles.moodTypeLabelText}>
111
+ {I18n.getLang(
112
+ isDynamic ? 'mood_overview_field_chip_2' : 'mood_overview_field_chip_text'
113
+ )}
114
+ </Text>
115
+ </View>
116
+ </View>
117
+ <Spacer height={cx(16)} />
118
+ </View>
119
+ </Card>
120
+ );
121
+ };
122
+
123
+ export default withTheme(MoodItem)
124
+
125
+ export function MixMoodColorsLine(props: { mixSubLight: MoodLampInfo; isMix: boolean, type: 'gradient' | 'separate' }) {
126
+ const { mixSubLight, isMix } = props;
127
+ const lightColors = !!(mixSubLight.enable && mixSubLight.nodes.length) ? mixSubLight.nodes?.map(n => {
128
+ const s = Math.round(mapFloatToRange(n.s / 100, 30, 100));
129
+ return n.isColorNode
130
+ ? hsv2Hex(n.h, s, Math.round(mapFloatToRange(n.v / 100, 50, 100)))
131
+ : cctToColor(n.colorTemp.toFixed());
132
+ }) : ['#eee'];
133
+
134
+ const styles = StyleSheet.create({
135
+ gradientItem: {
136
+ flexDirection: 'row',
137
+ },
138
+ gradientItemIconView: {
139
+ width: cx(24),
140
+ height: cx(24),
141
+ justifyContent: 'center',
142
+ alignItems: 'center',
143
+ backgroundColor: '#aaa',
144
+ borderRadius: cx(8),
145
+ },
146
+ gradientItemIcon: {
147
+ width: cx(16),
148
+ height: cx(16),
149
+ tintColor: '#fff',
150
+ },
151
+ })
152
+
153
+ return (
154
+ <View style={styles.gradientItem}>
155
+ <Spacer height={0} width={cx(16)} />
156
+ <MoodColorsLine
157
+ nodeStyle={{ borderColor: '#ccc', borderWidth: 1 }}
158
+ width={isMix ? cx(264) : undefined}
159
+ type={props.type}
160
+ colors={lightColors}
161
+ />
162
+ {isMix && (
163
+ <>
164
+ <Spacer height={0} width={cx(7)} />
165
+ <View style={styles.gradientItemIconView}>
166
+ <Image style={styles.gradientItemIcon} source={{uri: mixSubLight.enable ? res.light_on : res.light_off}} />
167
+ </View>
168
+ </>
169
+ )}
170
+ </View>
171
+ );
172
+ }