@ledvance/ui-biz-bundle 1.1.162 → 1.1.164

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.162",
7
+ "version": "1.1.164",
8
8
  "scripts": {},
9
9
  "dependencies": {
10
10
  "@ledvance/base": "^1.x",
@@ -16,6 +16,7 @@
16
16
  "prop-types": "^15.6.1",
17
17
  "react": "16.8.3",
18
18
  "react-native": "0.59.10",
19
+ "react-native-linear-gradient": "2.8.3",
19
20
  "react-native-orientation-locker": "^1.7.0",
20
21
  "react-native-svg": "5.5.1",
21
22
  "react-redux": "^7.2.1",
@@ -242,26 +242,37 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
242
242
  <Page
243
243
  backText={deviceInfo.name}
244
244
  onBackClick={navigation.goBack}
245
- headlineTopContent={<View style={{ flexDirection: 'row', width: '100%', justifyContent: 'space-between' }}>
246
- <DeleteButton style={{flex: 1}} text={I18n.getLang('biorhythm_save_as')} onPress={() => {
247
- Dialog.prompt({
248
- title: I18n.getLang('biorhythm_save_title'),
249
- placeholder: I18n.getLang('biorhythm_save_placeholder'),
250
- defaultValue: `${deviceInfo.name}`,
251
- cancelText: I18n.getLang('auto_scan_system_cancel'),
252
- confirmText: I18n.getLang('auto_scan_system_wifi_confirm'),
253
- inputWrapperStyle: {backgroundColor: props.theme?.textInput.background, borderRadius: cx(10)},
254
- autoFocus: true,
255
- onConfirm: (data, { close }) => {
256
- saveStorageBiorhythms(data, state.enable, state.gradient, state.weeks, state.planList)
257
- close()
258
- }
259
- })
260
- }} />
261
- <DeleteButton style={{flex: 1}} text={I18n.getLang('biorhythm_load')} onPress={() => {
262
- setBiorhythmListVisible(true)
263
- }} />
264
- </View>}
245
+ headlineTopContent={
246
+ <View style={{flexDirection: 'row', justifyContent: 'flex-end'}}>
247
+ <TouchableOpacity
248
+ style={{paddingLeft: cx(16)}}
249
+ onPress={() => {
250
+ Dialog.prompt({
251
+ title: I18n.getLang('biorhythm_save_title'),
252
+ placeholder: I18n.getLang('biorhythm_save_placeholder'),
253
+ defaultValue: `${deviceInfo.name}`,
254
+ cancelText: I18n.getLang('auto_scan_system_cancel'),
255
+ confirmText: I18n.getLang('auto_scan_system_wifi_confirm'),
256
+ inputWrapperStyle: {backgroundColor: props.theme?.textInput.background, borderRadius: cx(10)},
257
+ autoFocus: true,
258
+ onConfirm: (data, { close }) => {
259
+ saveStorageBiorhythms(data, state.enable, state.gradient, state.weeks, state.planList)
260
+ close()
261
+ }
262
+ })
263
+ }}
264
+ >
265
+ <Image source={res.icon_save} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
266
+ </TouchableOpacity>
267
+ <TouchableOpacity
268
+ style={{paddingLeft: cx(16)}}
269
+ onPress={() => {
270
+ setBiorhythmListVisible(true)
271
+ }}
272
+ >
273
+ <Image source={res.icon_import} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
274
+ </TouchableOpacity>
275
+ </View>}
265
276
  headlineText={I18n.getLang('add_new_trigger_time_system_back_text')}
266
277
  headlineIconContent={<Switch
267
278
  value={state.enable}
@@ -189,6 +189,9 @@ const DiyScenePage = (props: { theme?: ThemeType }) => {
189
189
  infoLine: {
190
190
  marginHorizontal: cx(24),
191
191
  },
192
+ rightIconView: {
193
+ flexDirection: 'row'
194
+ },
192
195
  addMoodPopover: {
193
196
  position: 'absolute',
194
197
  right: cx(60),
@@ -201,6 +204,9 @@ const DiyScenePage = (props: { theme?: ThemeType }) => {
201
204
  alignItems: 'flex-start',
202
205
  alignSelf: 'flex-start'
203
206
  },
207
+ refresh: {
208
+ paddingLeft: cx(24)
209
+ },
204
210
  })
205
211
 
206
212
  return (
@@ -208,10 +214,33 @@ const DiyScenePage = (props: { theme?: ThemeType }) => {
208
214
  <Page
209
215
  backText={deviceInfo.name}
210
216
  headlineText={I18n.getLang('mood_overview_headline_text')}
211
- headlineIcon={state.originScenes.length < MAX_MOOD_COUNT ? res.add : undefined}
212
- onHeadlineIconClick={() => {
213
- navigationRoute('add', cloneDeep(DEFAULT_SCENE))
214
- }}
217
+ headlineIconContent={<View style={styles.rightIconView}>
218
+ <TouchableOpacity
219
+ style={styles.refresh}
220
+ onPress={() => {
221
+ showDialog({
222
+ method: 'confirm',
223
+ title: I18n.getLang('mood_resetbutton'),
224
+ subTitle: I18n.getLang('reset_mooddescription'),
225
+ onConfirm: async (_, { close }) => {
226
+ close()
227
+ await getRemoteMixSceneInfo(true)
228
+ }
229
+ })
230
+ }}
231
+ >
232
+ <Image source={{uri: res.ic_refresh}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
233
+ </TouchableOpacity>
234
+ {state.originScenes.length < MAX_MOOD_COUNT && <TouchableOpacity
235
+ style={styles.refresh}
236
+ onPress={() => {
237
+ navigationRoute('add', cloneDeep(DEFAULT_SCENE))
238
+ }}
239
+ >
240
+ <Image source={{uri: res.add}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.brand }} />
241
+ </TouchableOpacity>
242
+ }
243
+ </View>}
215
244
  loading={state.loading}
216
245
  >
217
246
  <View style={styles.tagLine}>
@@ -239,21 +268,6 @@ const DiyScenePage = (props: { theme?: ThemeType }) => {
239
268
  }}
240
269
  />
241
270
  </View>
242
- <TouchableOpacity style={{ alignItems: 'flex-end',paddingRight: cx(24) }}
243
- onPress={() => {
244
- showDialog({
245
- method: 'confirm',
246
- title: I18n.getLang('mood_resetbutton'),
247
- subTitle: I18n.getLang('reset_mooddescription'),
248
- onConfirm: async (_, { close }) => {
249
- close()
250
- await getRemoteMixSceneInfo(true)
251
- }
252
- })
253
- }}
254
- >
255
- <Image source={{uri: res.ic_refresh}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
256
- </TouchableOpacity>
257
271
  <Spacer height={cx(10)} />
258
272
  {state.originScenes.length >= MAX_MOOD_COUNT && (
259
273
  <View style={styles.infoLine}>
@@ -78,8 +78,6 @@ const BarChartWithTouch = (props: BarChartProps) => {
78
78
  rotate: 45,
79
79
  align: 'right',
80
80
  verticalAlign: 'top',
81
- showMinLabel: true,
82
- showMaxLabel: true,
83
81
  }
84
82
  },
85
83
  yAxis: [
@@ -0,0 +1,102 @@
1
+ import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine'
2
+ import Spacer from '@ledvance/base/src/components/Spacer'
3
+ import res from '@ledvance/base/src/res'
4
+ import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils'
5
+ import { cctToColor } from '@ledvance/base/src/utils/cctUtils'
6
+ import React, { useState } from 'react'
7
+ import { Image, LayoutChangeEvent, StyleSheet, View } from 'react-native'
8
+ import { Utils } from 'tuya-panel-kit'
9
+ import { MoodLampInfo } from './Interface'
10
+
11
+ const cx = Utils.RatioUtils.convertX;
12
+ const { withTheme } = Utils.ThemeUtils;
13
+
14
+ const MixMoodColorsLine = (props: {
15
+ mixSubLight: MoodLampInfo;
16
+ isMix: boolean;
17
+ type: 'gradient' | 'separate';
18
+ width?: number; // 外部传入的宽度(父组件已计算好)
19
+ }) => {
20
+ const { mixSubLight, isMix, type, width: propWidth } = props;
21
+ const [measuredWidth, setMeasuredWidth] = useState(0);
22
+
23
+ const handleLayout = (event: LayoutChangeEvent) => {
24
+ const { width } = event.nativeEvent.layout;
25
+ if (width > 0 && width !== measuredWidth) {
26
+ setMeasuredWidth(width);
27
+ }
28
+ };
29
+
30
+ const lightColors = (mixSubLight.enable && mixSubLight.nodes.length > 0)
31
+ ? mixSubLight.nodes.map(n => {
32
+ const s = Math.round(mapFloatToRange(n.s / 100, 30, 100));
33
+ return n.isColorNode
34
+ ? hsv2Hex(n.h, s, Math.round(mapFloatToRange(n.v / 100, 50, 100)))
35
+ : cctToColor(n.colorTemp.toFixed());
36
+ })
37
+ : ['#eee'];
38
+
39
+ // 如果父组件传入了精确宽度,直接使用;否则使用内部测量的宽度。
40
+ const finalWidth = propWidth || measuredWidth;
41
+
42
+ // 渲染颜色条的通用逻辑
43
+ const renderColorLine = () => (
44
+ finalWidth > 0 && (
45
+ <MoodColorsLine
46
+ nodeStyle={{ borderColor: '#ccc', borderWidth: 1 }}
47
+ width={finalWidth}
48
+ type={type}
49
+ colors={lightColors}
50
+ />
51
+ )
52
+ );
53
+
54
+ // 渲染右侧图标的通用逻辑
55
+ const renderIcon = () => (
56
+ isMix && (
57
+ <>
58
+ <Spacer width={cx(7)}/>
59
+ <View style={styles.mixLineIconView}>
60
+ <Image style={styles.mixLineIcon} source={{ uri: mixSubLight.enable ? res.light_on : res.light_off }}/>
61
+ </View>
62
+ </>
63
+ )
64
+ );
65
+
66
+ // Case 1: 父组件已经计算并传入了宽度 (最高效)
67
+ if (propWidth) {
68
+ return (
69
+ <View style={styles.mixLineRow}>
70
+ {renderColorLine()}
71
+ {renderIcon()}
72
+ </View>
73
+ );
74
+ }
75
+
76
+ // Case 2: 自我测量模式 (回退方案)
77
+ return (
78
+ <View style={styles.mixLineRow}>
79
+ {/* 这个 View (测量器) 会自动收缩以填充'颜色条'应占的空间 */}
80
+ <View style={{ flex: 1 }} onLayout={handleLayout}>
81
+ {renderColorLine()}
82
+ </View>
83
+ {/* 图标作为测量器的兄弟节点,Flexbox 会先为它分配空间 */}
84
+ {renderIcon()}
85
+ </View>
86
+ );
87
+ }
88
+
89
+ const styles = StyleSheet.create({
90
+ mixLineRow: { flexDirection: 'row', alignItems: 'center' },
91
+ mixLineIconView: {
92
+ width: cx(24),
93
+ height: cx(24),
94
+ justifyContent: 'center',
95
+ alignItems: 'center',
96
+ backgroundColor: '#aaa',
97
+ borderRadius: cx(8),
98
+ },
99
+ mixLineIcon: { width: cx(16), height: cx(16), tintColor: '#fff' },
100
+ });
101
+
102
+ export default withTheme(MixMoodColorsLine)
@@ -108,14 +108,6 @@ function getRGBWDefSceneList(): RemoteMoodInfo[] {
108
108
  t: 0,
109
109
  e: false,
110
110
  },
111
- {
112
- n: I18n.getLang('mesh_device_detail_lighting_color_mode'),
113
- i:
114
- '05464601000003e803e800000000464601007803e803e80000000046460100f003e803e800000000464601003d03e803e80000000046460100ae03e803e800000000464601011303e803e800000000',
115
- s: '',
116
- t: 0,
117
- e: false,
118
- },
119
111
  {
120
112
  n: I18n.getLang('mesh_device_detail_lighting_white_mode'),
121
113
  i: '0646460100000000000003e8000046460100000000000003e8019046460100000000000003e803e8',
@@ -156,13 +148,6 @@ function getRGBDefSceneList(): RemoteMoodInfo[] {
156
148
  s: '',
157
149
  t: 0,
158
150
  e: false,
159
- },
160
- {
161
- n: I18n.getLang('mesh_device_detail_lighting_color_mode'),
162
- i: '05464601000003e803e800000000464601007803e803e80000000046460100f003e803e800000000464601003d03e803e80000000046460100ae03e803e800000000464601011303e803e800000000',
163
- s: '',
164
- t: 0,
165
- e: false,
166
151
  },
167
152
  ...defColorSceneList,
168
153
  ];
@@ -198,14 +183,6 @@ function getOnlyRGBDefSceneList(): RemoteMoodInfo[] {
198
183
  t: 0,
199
184
  e: false,
200
185
  },
201
- {
202
- n: I18n.getLang('mesh_device_detail_lighting_color_mode'),
203
- i:
204
- '05464601000003e803e800000000464601007803e803e80000000046460100f003e803e800000000464601003d03e803e80000000046460100ae03e803e800000000464601011303e803e800000000',
205
- s: '',
206
- t: 0,
207
- e: false,
208
- },
209
186
  ...defColorSceneList,
210
187
  ];
211
188
  }
@@ -521,6 +498,55 @@ function getStringLightSceneList(): RemoteMoodInfo[] {
521
498
  t: 0,
522
499
  e: false,
523
500
  },
501
+ {
502
+ n: I18n.getLang('strip_lights_mood_text45'),
503
+ i: '0125101e1e0000003700a032000000003700c3640000000037011364000000003700782800000000',
504
+ s: '',
505
+ t: 0,
506
+ e: false,
507
+ },
508
+ {
509
+ n: I18n.getLang('strip_lights_mood_text46'),
510
+ i: '01261014140000002d00174a000000002d002348000000002d001e24000000002d00014000000000',
511
+ s: '',
512
+ t: 0,
513
+ e: false,
514
+ },
515
+ {
516
+ n: I18n.getLang('strip_lights_mood_text47'),
517
+ i: '01270a555500000055012c64000000005500b4640000000055010f51000000005501485c00000000',
518
+ s: '',
519
+ t: 0,
520
+ e: false,
521
+ },
522
+ {
523
+ n: I18n.getLang('strip_lights_mood_text48'),
524
+ i: '01281028280000003700d258000000003700b564000000003700b152000000003700f06400000000',
525
+ s: '',
526
+ t: 0,
527
+ e: false,
528
+ },
529
+ {
530
+ n: I18n.getLang('strip_lights_mood_text49'),
531
+ i: '0129100f0f0000002300784c000000002300504b0000000023005238000000002300545200000000',
532
+ s: '',
533
+ t: 0,
534
+ e: false,
535
+ },
536
+ {
537
+ n: I18n.getLang('strip_lights_mood_text50'),
538
+ i: '012a0a5050000000640000640000000064007864000000006400f0640000000064003c640000000064012c6400000000',
539
+ s: '',
540
+ t: 0,
541
+ e: false,
542
+ },
543
+ {
544
+ n: I18n.getLang('strip_lights_mood_text51'),
545
+ i: '012b1032320000001e00f04e000000001e011364000000001e00b42900000000',
546
+ s: '',
547
+ t: 0,
548
+ e: false,
549
+ },
524
550
  ];
525
551
  }
526
552
 
@@ -834,6 +860,55 @@ function getStripLightSceneList(): RemoteMoodInfo[] {
834
860
  t: 0,
835
861
  e: false,
836
862
  },
863
+ {
864
+ n: I18n.getLang('strip_lights_mood_text45'),
865
+ i: '013b011e1e6000003700a03200c364011364007828',
866
+ s: '',
867
+ t: 0,
868
+ e: false,
869
+ },
870
+ {
871
+ n: I18n.getLang('strip_lights_mood_text46'),
872
+ i: '013c0114146000002d00174a002348001e24000140',
873
+ s: '',
874
+ t: 0,
875
+ e: false,
876
+ },
877
+ {
878
+ n: I18n.getLang('strip_lights_mood_text47'),
879
+ i: '013d02555560000055012c6400b464010f5101485c',
880
+ s: '',
881
+ t: 0,
882
+ e: false,
883
+ },
884
+ {
885
+ n: I18n.getLang('strip_lights_mood_text48'),
886
+ i: '013e0128286000003700d25800b56400b15200f064',
887
+ s: '',
888
+ t: 0,
889
+ e: false,
890
+ },
891
+ {
892
+ n: I18n.getLang('strip_lights_mood_text49'),
893
+ i: '013f010f0f6000002300784c00504b005238005452',
894
+ s: '',
895
+ t: 0,
896
+ e: false,
897
+ },
898
+ {
899
+ n: I18n.getLang('strip_lights_mood_text50'),
900
+ i: '01400250506000006400006400786400f064003c64012c64',
901
+ s: '',
902
+ t: 0,
903
+ e: false,
904
+ },
905
+ {
906
+ n: I18n.getLang('strip_lights_mood_text51'),
907
+ i: '01410132326000001e00f04e01136400b429',
908
+ s: '',
909
+ t: 0,
910
+ e: false,
911
+ },
837
912
  ];
838
913
  }
839
914
 
@@ -1175,6 +1250,55 @@ function getMixCeilingLightList(): RemoteMoodInfo[] {
1175
1250
  t: 0,
1176
1251
  e: false,
1177
1252
  },
1253
+ {
1254
+ n: I18n.getLang('strip_lights_mood_text45'),
1255
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1256
+ s: '013b011e1e6000003700a03200c364011364007828',
1257
+ t: 0,
1258
+ e: false,
1259
+ },
1260
+ {
1261
+ n: I18n.getLang('strip_lights_mood_text46'),
1262
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1263
+ s: '013c0114146000002d00174a002348001e24000140',
1264
+ t: 0,
1265
+ e: false,
1266
+ },
1267
+ {
1268
+ n: I18n.getLang('strip_lights_mood_text47'),
1269
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1270
+ s: '013d02555560000055012c6400b464010f5101485c',
1271
+ t: 0,
1272
+ e: false,
1273
+ },
1274
+ {
1275
+ n: I18n.getLang('strip_lights_mood_text48'),
1276
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1277
+ s: '013e0128286000003700d25800b56400b15200f064',
1278
+ t: 0,
1279
+ e: false,
1280
+ },
1281
+ {
1282
+ n: I18n.getLang('strip_lights_mood_text49'),
1283
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1284
+ s: '013f010f0f6000002300784c00504b005238005452',
1285
+ t: 0,
1286
+ e: false,
1287
+ },
1288
+ {
1289
+ n: I18n.getLang('strip_lights_mood_text50'),
1290
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1291
+ s: '01400250506000006400006400786400f064003c64012c64',
1292
+ t: 0,
1293
+ e: false,
1294
+ },
1295
+ {
1296
+ n: I18n.getLang('strip_lights_mood_text51'),
1297
+ i: '040d0d0100000000000003e800000d0d0100000000000003e80000',
1298
+ s: '01410132326000001e00f04e01136400b429',
1299
+ t: 0,
1300
+ e: false,
1301
+ },
1178
1302
  ];
1179
1303
  }
1180
1304
 
@@ -1200,12 +1324,6 @@ function getDefMixLightSceneList(): MixRemoteMoodInfo[] {
1200
1324
  image: '',
1201
1325
  value: '00030101020e0d0001f401f40000',
1202
1326
  },
1203
- {
1204
- name: I18n.getLang('mesh_device_detail_lighting_color_mode'),
1205
- image: '',
1206
- value:
1207
- '00040000010603464601000003e803e8464601007803e803e846460100f003e803e8464601003d03e803e846460100ae03e803e8464601011303e803e8',
1208
- },
1209
1327
  {
1210
1328
  name: I18n.getLang('mesh_device_detail_lighting_white_mode'),
1211
1329
  image: '',
@@ -1429,6 +1547,55 @@ const defColorSceneList: RemoteMoodInfo[] = [
1429
1547
  t: 0,
1430
1548
  e: false,
1431
1549
  },
1550
+ {
1551
+ n: I18n.getLang('strip_lights_mood_text45'),
1552
+ i: '251e1e0200a001f40226000000001e1e0200c303e80226000000001e1e02011303e80226000000001e1e0200780190022600000000',
1553
+ s: '',
1554
+ t: 0,
1555
+ e: false,
1556
+ },
1557
+ {
1558
+ n: I18n.getLang('strip_lights_mood_text46'),
1559
+ i: '26141402001702e401c200000000141402002302d001c200000000141402001e016801c2000000001414020001028001c200000000',
1560
+ s: '',
1561
+ t: 0,
1562
+ e: false,
1563
+ },
1564
+ {
1565
+ n: I18n.getLang('strip_lights_mood_text47'),
1566
+ i: '27555501012c03e803520000000055550100b403e8035200000000555501010f032a03520000000055550101480398035200000000',
1567
+ s: '',
1568
+ t: 0,
1569
+ e: false,
1570
+ },
1571
+ {
1572
+ n: I18n.getLang('strip_lights_mood_text48'),
1573
+ i: '2828280200d2037002260000000028280200b503e802260000000028280200b1033402260000000028280200f003e8022600000000',
1574
+ s: '',
1575
+ t: 0,
1576
+ e: false,
1577
+ },
1578
+ {
1579
+ n: I18n.getLang('strip_lights_mood_text49'),
1580
+ i: '290f0f02007802f8015e000000000f0f02005002ee015e000000000f0f0200520230015e000000000f0f0200540334015e00000000',
1581
+ s: '',
1582
+ t: 0,
1583
+ e: false,
1584
+ },
1585
+ {
1586
+ n: I18n.getLang('strip_lights_mood_text50'),
1587
+ i: '2a505001000003e803e800000000505001007803e803e80000000050500100f003e803e800000000505001003c03e803e800000000505001012c03e803e800000000',
1588
+ s: '',
1589
+ t: 0,
1590
+ e: false,
1591
+ },
1592
+ {
1593
+ n: I18n.getLang('strip_lights_mood_text51'),
1594
+ i: '2b32320200f0030c012c00000000323202011303e8012c0000000032320200b4019a012c00000000',
1595
+ s: '',
1596
+ t: 0,
1597
+ e: false,
1598
+ },
1432
1599
  ];
1433
1600
 
1434
1601
  const defMixColorSceneList: MixRemoteMoodInfo[] = [
@@ -1582,6 +1749,41 @@ const defMixColorSceneList: MixRemoteMoodInfo[] = [
1582
1749
  image: '',
1583
1750
  value: '002400000103034b4b0200d203e803e84b4b0200b403e803e84b4b02009603e803e8',
1584
1751
  },
1752
+ {
1753
+ name: I18n.getLang('strip_lights_mood_text45'),
1754
+ image: '',
1755
+ value: '002500000104031e1e0200a001f402261e1e0200c303e802261e1e02011303e802261e1e02007801900226',
1756
+ },
1757
+ {
1758
+ name: I18n.getLang('strip_lights_mood_text46'),
1759
+ image: '',
1760
+ value: '00260000010403141402001702e401c2141402002302d001c2141402001e016801c21414020001028001c2',
1761
+ },
1762
+ {
1763
+ name: I18n.getLang('strip_lights_mood_text47'),
1764
+ image: '',
1765
+ value: '00270000010403555501012c03e8035255550100b403e80352555501010f032a0352555501014803980352',
1766
+ },
1767
+ {
1768
+ name: I18n.getLang('strip_lights_mood_text48'),
1769
+ image: '',
1770
+ value: '0028000001040328280200d20370022628280200b503e8022628280200b10334022628280200f003e80226',
1771
+ },
1772
+ {
1773
+ name: I18n.getLang('strip_lights_mood_text49'),
1774
+ image: '',
1775
+ value: '002900000104030f0f02007802f8015e0f0f02005002ee015e0f0f0200520230015e0f0f0200540334015e',
1776
+ },
1777
+ {
1778
+ name: I18n.getLang('strip_lights_mood_text50'),
1779
+ image: '',
1780
+ value: '002a0000010503505001000003e803e8505001007803e803e850500100f003e803e8505001003c03e803e8505001012c03e803e8',
1781
+ },
1782
+ {
1783
+ name: I18n.getLang('strip_lights_mood_text51'),
1784
+ image: '',
1785
+ value: '002b000001030332320200f0030c012c323202011303e8012c32320200b4019a012c',
1786
+ },
1585
1787
  ];
1586
1788
 
1587
1789
  export type RecommendMood = {
@@ -1,15 +1,13 @@
1
- import Card from '@ledvance/base/src/components/Card';
2
- import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine';
3
- import Spacer from '@ledvance/base/src/components/Spacer';
4
1
  import ThemeType from '@ledvance/base/src/config/themeType';
5
2
  import I18n from '@ledvance/base/src/i18n';
6
3
  import res from '@ledvance/base/src/res';
7
4
  import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils';
8
5
  import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
9
- import React, { useMemo, useState } from 'react';
10
- import { Image, LayoutChangeEvent, StyleSheet, Text, TouchableOpacity, View, ViewProps, ViewStyle } from 'react-native';
6
+ import React, { useMemo } from 'react';
7
+ import { Image, Platform, StyleSheet, Text, TouchableOpacity, View, ViewProps, ViewStyle } from 'react-native';
8
+ import LinearGradient from 'react-native-linear-gradient';
11
9
  import { Utils } from 'tuya-panel-kit';
12
- import { MoodJumpGradientMode, MoodLampInfo, MoodUIInfo } from './Interface';
10
+ import { MoodLampInfo, MoodUIInfo } from './Interface';
13
11
 
14
12
  const cx = Utils.RatioUtils.convertX;
15
13
  const { withTheme } = Utils.ThemeUtils;
@@ -32,189 +30,195 @@ interface MoodItemProps extends ViewProps {
32
30
  onSwitch: (enable: boolean) => void;
33
31
  }
34
32
 
33
+ const getGradientColors = (lampInfo: MoodLampInfo, defaultColors: string[]): string[] => {
34
+ if (!lampInfo.enable || lampInfo.nodes.length === 0) {
35
+ return defaultColors;
36
+ }
37
+
38
+ const colors = lampInfo.nodes.map(n => {
39
+ const s = Math.round(mapFloatToRange(n.s / 100, 30, 70));
40
+ const v = Math.round(mapFloatToRange(n.v / 100, 80, 100));
41
+ return n.isColorNode ? hsv2Hex(n.h, s, v) : cctToColor(n.colorTemp.toFixed());
42
+ });
43
+
44
+ if (colors.length === 1) {
45
+ return [colors[0], colors[0]];
46
+ }
47
+
48
+ return colors;
49
+ };
50
+
35
51
  const MoodItem = (props: MoodItemProps) => {
36
- const { mood, isMix, deviceTypeOption, theme } = props;
52
+ const { mood, isMix, deviceTypeOption, theme, onPress, onSwitch, enable, style } = props;
53
+ const styles = getStyles(theme);
54
+
37
55
  const isDynamic = useMemo(() => {
38
56
  return mood.mainLamp.nodes?.length > 1 || mood.secondaryLamp.nodes?.length > 1;
39
57
  }, [mood.mainLamp.nodes, mood.secondaryLamp.nodes]);
40
58
 
41
- const gradientMode = useMemo(() => (
42
- deviceTypeOption?.isStringLight ? MoodJumpGradientMode.StringGradient : deviceTypeOption?.isStripLight ? MoodJumpGradientMode.StripGradient : MoodJumpGradientMode.SourceGradient
43
- ), [MoodJumpGradientMode, deviceTypeOption]);
59
+ const isDarkMode = theme?.type === 'dark';
60
+ const defaultGreyGradient = isDarkMode ? ['#444444', '#333333'] : ['#E5E5E5', '#DCDCDC'];
44
61
 
45
- const styles = useMemo(() => getStyles(theme), [theme]);
62
+ const mainLampColors = useMemo(
63
+ () => getGradientColors(mood.mainLamp, defaultGreyGradient),
64
+ [mood.mainLamp, defaultGreyGradient]
65
+ );
66
+ const secondaryLampColors = useMemo(
67
+ () => getGradientColors(mood.secondaryLamp, defaultGreyGradient),
68
+ [mood.secondaryLamp, defaultGreyGradient]
69
+ );
46
70
 
47
- return (
48
- <Card style={[styles.card, props.style]} onPress={props.onPress}>
49
- <View style={styles.contentContainer}>
50
- <View style={styles.row}>
51
- <Text style={styles.headText}>{mood.name}</Text>
52
- <TouchableOpacity style={styles.checkbox} onPress={() => props.onSwitch(!props.enable)}>
53
- <Image source={{ uri: res.ic_check }}
54
- style={[styles.checkboxImage, { tintColor: props.enable ? theme?.icon.primary : theme?.icon.disable }]}/>
55
- </TouchableOpacity>
56
- </View>
57
- <Spacer height={cx(8)}/>
58
- <MixMoodColorsLine
59
- mixSubLight={mood.mainLamp}
60
- isMix={isMix}
61
- type={mood.mainLamp.mode === gradientMode && !deviceTypeOption?.isCeilingLight ? 'gradient' : 'separate'}
62
- />
63
- {(deviceTypeOption?.isMixLight || (isMix && !!mood.secondaryLamp.nodes.length)) && (
64
- <>
65
- <Spacer height={cx(7)}/>
66
- <MixMoodColorsLine
67
- mixSubLight={mood.secondaryLamp}
68
- isMix={isMix}
69
- type={mood.secondaryLamp.mode === (deviceTypeOption?.isMixLight ? MoodJumpGradientMode.SourceGradient : MoodJumpGradientMode.StripGradient) ? 'gradient' : 'separate'}
70
- />
71
- </>
72
- )}
73
- <Spacer height={cx(12)}/>
74
- <View style={styles.row}>
75
- <View style={styles.moodTypeLabel}>
76
- <Text style={styles.moodTypeLabelText}>
77
- {I18n.getLang(isDynamic ? 'mood_overview_field_chip_2' : 'mood_overview_field_chip_text')}
78
- </Text>
79
- </View>
71
+ const isMixLight = isMix || deviceTypeOption?.isMixLight;
72
+
73
+ const hasMainLampColors = mood.mainLamp.enable && mood.mainLamp.nodes.length > 0;
74
+ const hasSecondaryLampColors = mood.secondaryLamp.enable && mood.secondaryLamp.nodes.length > 0;
75
+
76
+ const renderContent = () => (
77
+ // 关键修改 1: contentContainer 使用 justifyContent: 'space-between'
78
+ <View style={styles.contentContainer}>
79
+ {/* 顶部内容 */}
80
+ <View style={styles.row}>
81
+ <Text style={styles.headText}>{mood.name}</Text>
82
+ {/* checkbox 的 TouchableOpacity 现在也应用了阴影样式 */}
83
+ <TouchableOpacity style={styles.checkbox} onPress={() => onSwitch(!enable)}>
84
+ <Image
85
+ source={{ uri: res.ic_check }}
86
+ style={[styles.checkboxImage, { tintColor: enable ? theme?.icon.primary : theme?.icon.disable }]}
87
+ />
88
+ </TouchableOpacity>
89
+ </View>
90
+
91
+ {/* 底部内容 (移除了 Spacer) */}
92
+ <View style={styles.row}>
93
+ <View style={styles.moodTypeLabel}>
94
+ <Text style={styles.moodTypeLabelText}>
95
+ {I18n.getLang(isDynamic ? 'mood_overview_field_chip_2' : 'mood_overview_field_chip_text')}
96
+ </Text>
80
97
  </View>
81
98
  </View>
82
- </Card>
99
+ </View>
83
100
  );
84
- };
85
101
 
86
- // --- 【核心修正】 ---
87
- export function MixMoodColorsLine(props: {
88
- mixSubLight: MoodLampInfo;
89
- isMix: boolean;
90
- type: 'gradient' | 'separate';
91
- width?: number; // 外部传入的宽度(父组件已计算好)
92
- }) {
93
- const { mixSubLight, isMix, type, width: propWidth } = props;
94
- const [measuredWidth, setMeasuredWidth] = useState(0);
95
-
96
- const handleLayout = (event: LayoutChangeEvent) => {
97
- const { width } = event.nativeEvent.layout;
98
- if (width > 0 && width !== measuredWidth) {
99
- setMeasuredWidth(width);
102
+ const renderBackground = () => {
103
+ if (!isMixLight) {
104
+ return <LinearGradient colors={mainLampColors} style={{ flex: 1 }} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} />;
100
105
  }
101
- };
102
106
 
103
- const lightColors = (mixSubLight.enable && mixSubLight.nodes.length > 0)
104
- ? mixSubLight.nodes.map(n => {
105
- const s = Math.round(mapFloatToRange(n.s / 100, 30, 100));
106
- return n.isColorNode
107
- ? hsv2Hex(n.h, s, Math.round(mapFloatToRange(n.v / 100, 50, 100)))
108
- : cctToColor(n.colorTemp.toFixed());
109
- })
110
- : ['#eee'];
111
-
112
- // 如果父组件传入了精确宽度,直接使用;否则使用内部测量的宽度。
113
- const finalWidth = propWidth || measuredWidth;
114
-
115
- // 渲染颜色条的通用逻辑
116
- const renderColorLine = () => (
117
- finalWidth > 0 && (
118
- <MoodColorsLine
119
- nodeStyle={{ borderColor: '#ccc', borderWidth: 1 }}
120
- width={finalWidth}
121
- type={type}
122
- colors={lightColors}
123
- />
124
- )
125
- );
107
+ if (hasMainLampColors && hasSecondaryLampColors) {
108
+ return (
109
+ <>
110
+ <LinearGradient colors={mainLampColors} style={{ flex: 1 }} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} />
111
+ <LinearGradient
112
+ colors={secondaryLampColors}
113
+ style={{ flex: 1 }}
114
+ start={{ x: 0, y: 0.5 }}
115
+ end={{ x: 1, y: 0.5 }}
116
+ />
117
+ </>
118
+ );
119
+ }
126
120
 
127
- // 渲染右侧图标的通用逻辑
128
- const renderIcon = () => (
129
- isMix && (
130
- <>
131
- <Spacer width={cx(7)}/>
132
- <View style={styles.mixLineIconView}>
133
- <Image style={styles.mixLineIcon} source={{ uri: mixSubLight.enable ? res.light_on : res.light_off }}/>
134
- </View>
135
- </>
136
- )
137
- );
121
+ if (hasMainLampColors) {
122
+ return <LinearGradient colors={mainLampColors} style={{ flex: 1 }} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} />;
123
+ }
138
124
 
139
- // Case 1: 父组件已经计算并传入了宽度 (最高效)
140
- if (propWidth) {
141
- return (
142
- <View style={styles.mixLineRow}>
143
- {renderColorLine()}
144
- {renderIcon()}
145
- </View>
146
- );
147
- }
125
+ if (hasSecondaryLampColors) {
126
+ return (
127
+ <LinearGradient colors={secondaryLampColors} style={{ flex: 1 }} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} />
128
+ );
129
+ }
130
+
131
+ return <LinearGradient colors={mainLampColors} style={{ flex: 1 }} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} />;
132
+ };
148
133
 
149
- // Case 2: 自我测量模式 (回退方案)
150
134
  return (
151
- <View style={styles.mixLineRow}>
152
- {/* 这个 View (测量器) 会自动收缩以填充'颜色条'应占的空间 */}
153
- <View style={{ flex: 1 }} onLayout={handleLayout}>
154
- {renderColorLine()}
155
- </View>
156
- {/* 图标作为测量器的兄弟节点,Flexbox 会先为它分配空间 */}
157
- {renderIcon()}
158
- </View>
135
+ <TouchableOpacity activeOpacity={0.8} onPress={onPress} style={[styles.container, style || { marginHorizontal: cx(24), }]}>
136
+ <View style={styles.backgroundWrapper}>{renderBackground()}</View>
137
+ <View style={StyleSheet.absoluteFill}>{renderContent()}</View>
138
+ </TouchableOpacity>
159
139
  );
160
- }
140
+ };
141
+
142
+ const getStyles = (theme?: ThemeType) => {
143
+ const isDarkMode = theme?.type === 'dark';
144
+ const primaryTextColor = isDarkMode ? '#FFFFFF' : '#000000';
145
+ const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)';
146
+ const tagBackgroundColor = isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'rgba(255, 255, 255, 0.5)';
147
+ const tagTextColor = isDarkMode ? '#FFFFFF' : '#333333';
148
+
149
+ return StyleSheet.create({
150
+ container: {
151
+ // marginHorizontal: cx(24),
152
+ height: cx(135),
153
+ ...Platform.select({
154
+ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8 },
155
+ android: { elevation: 5 },
156
+ }),
157
+ },
158
+ backgroundWrapper: {
159
+ flex: 1,
160
+ borderRadius: cx(16),
161
+ overflow: 'hidden',
162
+ },
163
+ // 关键修改 1: 使用 justifyContent 将内容推向两端
164
+ contentContainer: {
165
+ flex: 1,
166
+ paddingHorizontal: cx(16),
167
+ paddingVertical: cx(12),
168
+ backgroundColor: 'transparent',
169
+ justifyContent: 'space-between', // 使顶部和底部内容分别贴近上下边缘
170
+ },
171
+ row: {
172
+ flexDirection: 'row',
173
+ alignItems: 'center',
174
+ },
175
+ headText: {
176
+ flex: 1,
177
+ fontSize: cx(16),
178
+ lineHeight: cx(20),
179
+ fontWeight: 'bold',
180
+ color: primaryTextColor,
181
+ textShadowColor: shadowColor,
182
+ textShadowOffset: { width: 0, height: 1 },
183
+ textShadowRadius: 3,
184
+ },
185
+ // 关键修改 2: 为 checkbox 添加阴影
186
+ checkbox: {
187
+ width: cx(45),
188
+ height: cx(45),
189
+ marginRight: cx(-10),
190
+ justifyContent: 'center',
191
+ alignItems: 'flex-end',
192
+ // 添加一个通用的深色阴影以提供对比度
193
+ ...Platform.select({
194
+ ios: {
195
+ shadowColor: 'rgba(0, 0, 0, 0.4)',
196
+ shadowOffset: { width: 0, height: 1 },
197
+ shadowRadius: 2,
198
+ shadowOpacity: 1,
199
+ },
200
+ android: {
201
+ elevation: 3,
202
+ },
203
+ }),
204
+ },
205
+ checkboxImage: {
206
+ width: cx(44),
207
+ height: cx(44),
208
+ },
209
+ moodTypeLabel: {
210
+ paddingHorizontal: cx(12.5),
211
+ paddingVertical: cx(4),
212
+ borderRadius: cx(8),
213
+ backgroundColor: tagBackgroundColor,
214
+ },
215
+ moodTypeLabelText: {
216
+ fontSize: cx(10),
217
+ lineHeight: cx(12),
218
+ color: tagTextColor,
219
+ fontWeight: '500',
220
+ },
221
+ });
222
+ };
161
223
 
162
- const getStyles = (theme?: ThemeType) => StyleSheet.create({
163
- card: {
164
- marginHorizontal: cx(24),
165
- padding: 0,
166
- borderRadius: cx(16),
167
- },
168
- contentContainer: {
169
- paddingHorizontal: cx(16),
170
- paddingTop: cx(8),
171
- paddingBottom: cx(16),
172
- },
173
- row: {
174
- flexDirection: 'row',
175
- alignItems: 'center',
176
- },
177
- headText: {
178
- flex: 1,
179
- color: theme?.global.fontColor,
180
- fontSize: cx(16),
181
- lineHeight: cx(20),
182
- },
183
- checkbox: {
184
- width: cx(45),
185
- height: cx(45),
186
- marginRight: cx(-10),
187
- justifyContent: 'center',
188
- alignItems: 'flex-end',
189
- },
190
- checkboxImage: {
191
- width: cx(44),
192
- height: cx(44),
193
- },
194
- moodTypeLabel: {
195
- paddingHorizontal: cx(12.5),
196
- backgroundColor: theme?.tag.background,
197
- borderRadius: cx(8),
198
- },
199
- moodTypeLabelText: {
200
- height: cx(16),
201
- color: theme?.tag.fontColor,
202
- fontSize: cx(10),
203
- lineHeight: cx(16),
204
- },
205
- });
206
-
207
- const styles = StyleSheet.create({
208
- mixLineRow: { flexDirection: 'row', alignItems: 'center' },
209
- mixLineIconView: {
210
- width: cx(24),
211
- height: cx(24),
212
- justifyContent: 'center',
213
- alignItems: 'center',
214
- backgroundColor: '#aaa',
215
- borderRadius: cx(8),
216
- },
217
- mixLineIcon: { width: cx(16), height: cx(16), tintColor: '#fff' },
218
- });
219
-
220
- export default withTheme(MoodItem);
224
+ export default withTheme(MoodItem);
@@ -6,6 +6,7 @@ import {
6
6
  useDeviceId,
7
7
  useDeviceInfo,
8
8
  useFlagMode,
9
+ useIsPad,
9
10
  useMoods,
10
11
  } from '@ledvance/base/src/models/modules/NativePropsSlice';
11
12
  import { useReactive } from 'ahooks';
@@ -30,11 +31,15 @@ import I18n from '@ledvance/base/src/i18n';
30
31
  import ThemeType from '@ledvance/base/src/config/themeType'
31
32
 
32
33
 
33
- const cx = Utils.RatioUtils.convertX;
34
+ const { convertX: cx } = Utils.RatioUtils;
34
35
  const { withTheme } = Utils.ThemeUtils
35
36
 
36
37
  const MAX_MOOD_COUNT = 255;
37
38
 
39
+ // --- 关键修改 1: 定义新的布局常量 ---
40
+ const OPTIMAL_ITEM_WIDTH = cx(165); // 定义一个在所有设备上看起来都很好的“最佳宽度”
41
+ const GRID_GAP = cx(16);
42
+
38
43
  const MoodPage = (props: { theme?: ThemeType }) => {
39
44
  const params = useParams<MoodPageParams>();
40
45
  const deviceInfo = useDeviceInfo();
@@ -51,6 +56,7 @@ const MoodPage = (props: { theme?: ThemeType }) => {
51
56
  isSupportSceneStatus: params.isSupportSceneStatus,
52
57
  sceneStatusType: SceneStatusType.Mood
53
58
  });
59
+ const isPad = useIsPad(); // 使用 isPad hook
54
60
 
55
61
  const state = useReactive<MoodPageState>({
56
62
  currentMood: undefined,
@@ -64,6 +70,28 @@ const MoodPage = (props: { theme?: ThemeType }) => {
64
70
  flag: Symbol(),
65
71
  });
66
72
 
73
+ // --- 关键修改 2: 基于 isPad 决定列数,并计算出 FlatList 自身所需的总宽度 ---
74
+ const { numColumns, flatListWidth } = useMemo(() => {
75
+ const cols = isPad ? 3 : 2;
76
+ const listWidth = cols * OPTIMAL_ITEM_WIDTH + (cols - 1) * GRID_GAP;
77
+ return {
78
+ numColumns: cols,
79
+ flatListWidth: listWidth,
80
+ };
81
+ }, [isPad]);
82
+
83
+ // --- 关键修改 3: 准备幽灵元素数据 ---
84
+ const gridData = useMemo(() => {
85
+ const data = state.filterMoods;
86
+ const numToPad = numColumns - (data.length % numColumns);
87
+ if (numToPad === numColumns) {
88
+ return data;
89
+ }
90
+ const ghostItems = Array.from({ length: numToPad }, (_, i) => ({ name: `ghost-${i}`, isGhost: true }));
91
+ return [...data, ...ghostItems];
92
+ }, [state.filterMoods, numColumns]);
93
+
94
+ // ... (其余所有业务逻辑和状态管理保持不变) ...
67
95
  const moodIds = useMemo(() => {
68
96
  // @ts-ignore
69
97
  const mainIds = map(params.isCeilingLight ? (state.originMoods.map(m => m.mainLamp) || []) : state.originMoods, 'id').filter(v => v !== undefined)
@@ -307,6 +335,9 @@ const MoodPage = (props: { theme?: ThemeType }) => {
307
335
  infoLine: {
308
336
  marginHorizontal: cx(24),
309
337
  },
338
+ rightIconView: {
339
+ flexDirection: 'row'
340
+ },
310
341
  addMoodPopover: {
311
342
  position: 'absolute',
312
343
  right: cx(60),
@@ -319,6 +350,17 @@ const MoodPage = (props: { theme?: ThemeType }) => {
319
350
  alignItems: 'flex-start',
320
351
  alignSelf: 'flex-start'
321
352
  },
353
+ refresh: {
354
+ paddingLeft: cx(24)
355
+ },
356
+ columnWrapperStyle: {
357
+ justifyContent: 'space-between',
358
+ },
359
+ // --- 关键修改 4: 新增列表容器样式 ---
360
+ listContainer: {
361
+ flex: 1,
362
+ alignItems: 'center', // 让内部的 FlatList 水平居中
363
+ },
322
364
  })
323
365
 
324
366
  return (
@@ -326,14 +368,37 @@ const MoodPage = (props: { theme?: ThemeType }) => {
326
368
  <Page
327
369
  backText={deviceInfo.name}
328
370
  headlineText={Strings.getLang('mood_overview_headline_text')}
329
- headlineIcon={state.originMoods.length < MAX_MOOD_COUNT ? res.add : undefined}
330
- onHeadlineIconClick={() => {
331
- if (params.isStringLight || params.isStripLight) {
332
- onAddMoodDialogItemClick(false, 1);
333
- } else {
334
- state.showAddMoodPopover = !state.showAddMoodPopover;
371
+ headlineIconContent={<View style={styles.rightIconView}>
372
+ <TouchableOpacity
373
+ style={styles.refresh}
374
+ onPress={() => {
375
+ showDialog({
376
+ method: 'confirm',
377
+ title: I18n.getLang('mood_resetbutton'),
378
+ subTitle: I18n.getLang('reset_mooddescription'),
379
+ onConfirm: async (_, { close }) => {
380
+ close()
381
+ await getRemoteMoodInfo(true)
382
+ }
383
+ })
384
+ }}
385
+ >
386
+ <Image source={{uri: res.ic_refresh}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
387
+ </TouchableOpacity>
388
+ {state.originMoods.length < MAX_MOOD_COUNT && <TouchableOpacity
389
+ style={styles.refresh}
390
+ onPress={() => {
391
+ if (params.isStringLight || params.isStripLight) {
392
+ onAddMoodDialogItemClick(false, 1);
393
+ } else {
394
+ state.showAddMoodPopover = !state.showAddMoodPopover;
395
+ }
396
+ }}
397
+ >
398
+ <Image source={{uri: res.add}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.brand }} />
399
+ </TouchableOpacity>
335
400
  }
336
- }}
401
+ </View>}
337
402
  loading={state.loading}
338
403
  >
339
404
  {!(params.isStringLight || params.isStripLight) && <View style={styles.tagLine}>
@@ -353,21 +418,6 @@ const MoodPage = (props: { theme?: ThemeType }) => {
353
418
  }}
354
419
  />
355
420
  </View>}
356
- <TouchableOpacity style={{ alignItems: 'flex-end',paddingRight: cx(24) }}
357
- onPress={() => {
358
- showDialog({
359
- method: 'confirm',
360
- title: I18n.getLang('mood_resetbutton'),
361
- subTitle: I18n.getLang('reset_mooddescription'),
362
- onConfirm: async (_, { close }) => {
363
- close()
364
- await getRemoteMoodInfo(true)
365
- }
366
- })
367
- }}
368
- >
369
- <Image source={{uri: res.ic_refresh}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
370
- </TouchableOpacity>
371
421
  <Spacer height={cx(10)} />
372
422
  {state.originMoods.length >= MAX_MOOD_COUNT && (
373
423
  <View style={styles.infoLine}>
@@ -380,32 +430,45 @@ const MoodPage = (props: { theme?: ThemeType }) => {
380
430
  <Spacer height={cx(6)} />
381
431
  </View>
382
432
  )}
383
- <FlatList
384
- data={state.filterMoods}
385
- renderItem={({ item }) => {
386
- return (
387
- <MoodItem
388
- enable={getItemEnable(item)}
389
- isMix={!!(params.isMixLight || params.isCeilingLight)}
390
- mood={item}
391
- deviceTypeOption={params}
392
- onPress={() => {
393
- navigationRoute(item.mainLamp.nodes.length === 1, 'edit', item);
394
- }}
395
- onSwitch={async _ => {
396
- state.loading = true;
397
- await modDeleteMood('set', item);
398
- updateFlagMode();
399
- state.loading = false;
400
- }}
401
- />
402
- );
403
- }}
404
- ListHeaderComponent={() => <Spacer height={cx(10)} />}
405
- ItemSeparatorComponent={() => <Spacer />}
406
- ListFooterComponent={() => <Spacer />}
407
- keyExtractor={item => `${item.name}`}
408
- />
433
+
434
+ {/* --- 关键修改 5: 应用新的 FlatList 布局 --- */}
435
+ <View style={styles.listContainer}>
436
+ <FlatList
437
+ data={gridData}
438
+ numColumns={numColumns}
439
+ key={numColumns}
440
+ style={{ width: flatListWidth }}
441
+ columnWrapperStyle={styles.columnWrapperStyle}
442
+ renderItem={({ item }) => {
443
+ if (item.isGhost) {
444
+ return <View style={{ width: OPTIMAL_ITEM_WIDTH, marginBottom: cx(16) }} />;
445
+ }
446
+
447
+ return (
448
+ <MoodItem
449
+ style={{ width: OPTIMAL_ITEM_WIDTH }}
450
+ enable={getItemEnable(item)}
451
+ isMix={!!(params.isMixLight || params.isCeilingLight)}
452
+ mood={item}
453
+ deviceTypeOption={params}
454
+ onPress={() => {
455
+ navigationRoute(item.mainLamp.nodes.length === 1, 'edit', item);
456
+ }}
457
+ onSwitch={async _ => {
458
+ state.loading = true;
459
+ await modDeleteMood('set', item);
460
+ updateFlagMode();
461
+ state.loading = false;
462
+ }}
463
+ />
464
+ );
465
+ }}
466
+ ListHeaderComponent={() => <Spacer height={cx(10)} />}
467
+ ItemSeparatorComponent={() => <Spacer />}
468
+ ListFooterComponent={() => <Spacer />}
469
+ keyExtractor={item => `${item.name}`}
470
+ />
471
+ </View>
409
472
  </Page>
410
473
  <CustomListDialog
411
474
  show={state.showAddMoodPopover}
@@ -430,4 +493,4 @@ const MoodPage = (props: { theme?: ThemeType }) => {
430
493
  );
431
494
  };
432
495
 
433
- export default withTheme(MoodPage)
496
+ export default withTheme(MoodPage)
@@ -7,7 +7,7 @@ import Spacer from '@ledvance/base/src/components/Spacer';
7
7
  import ThemeType from '@ledvance/base/src/config/themeType';
8
8
 
9
9
  import { MoodJumpGradientMode, MoodUIInfo } from './Interface';
10
- import { MixMoodColorsLine } from './MoodItem'; // 从 MoodItem.tsx 导入重构后的组件
10
+ import MixMoodColorsLine from './MixMoodColorsLine';
11
11
 
12
12
  const cx = Utils.RatioUtils.convertX;
13
13
  const { withTheme } = Utils.ThemeUtils;
@@ -450,7 +450,7 @@ const TimeScheduleDetailPage = (props: { theme?: ThemeType }) => {
450
450
  }}
451
451
  />
452
452
  <Spacer />
453
- <Text style={{ ...styles.cardContainer, color: props.theme?.global.fontColor, fontSize: cx(14) }}>{loopText(state.timeSchedule.loops.split(''))}</Text>
453
+ <Text style={{ ...styles.cardContainer, color: props.theme?.global.fontColor, fontSize: cx(14) }}>{loopText(state.timeSchedule.loops.split(''), state.timeSchedule.time)}</Text>
454
454
  <Spacer height={cx(30)} />
455
455
 
456
456
  {/* Apply for */}