@ledvance/ui-biz-bundle 1.1.161 → 1.1.163
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 +2 -1
- package/src/newModules/biorhythm/BiorhythmActions.ts +92 -3
- package/src/newModules/biorhythm/BiorhythmPage.tsx +78 -29
- package/src/newModules/energyConsumption/component/PowerLineChart.tsx +48 -1
- package/src/newModules/mood/MixMoodColorsLine.tsx +102 -0
- package/src/newModules/mood/MoodInfo.ts +0 -29
- package/src/newModules/mood/MoodItem.tsx +177 -173
- package/src/newModules/mood/MoodPage.tsx +42 -13
- package/src/newModules/mood/RecommendMoodItem.tsx +1 -1
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.
|
|
7
|
+
"version": "1.1.163",
|
|
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",
|
|
@@ -8,18 +8,19 @@ import { hex2Int, spliceByStep } from '@ledvance/base/src/utils/common'
|
|
|
8
8
|
import { to16 } from '@tuya/tuya-panel-lamp-sdk/lib/utils'
|
|
9
9
|
import { useUpdateEffect } from 'ahooks'
|
|
10
10
|
import dayjs from 'dayjs'
|
|
11
|
-
import { padStart } from 'lodash'
|
|
11
|
+
import { cloneDeep, padStart } from 'lodash'
|
|
12
12
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
13
|
+
import { AsyncStorage } from 'react-native'
|
|
13
14
|
import {
|
|
14
15
|
BiorhythmBean,
|
|
15
16
|
BiorhythmGradientType,
|
|
16
17
|
BiorhythmGradientTypeMap,
|
|
17
18
|
BiorhythmGradientTypeMap2,
|
|
18
19
|
colorTemperatureValue,
|
|
19
|
-
colorTempPercent,
|
|
20
|
-
Plan,
|
|
20
|
+
colorTempPercent, Plan,
|
|
21
21
|
RemoteBiorhythmBean,
|
|
22
22
|
} from './BiorhythmBean'
|
|
23
|
+
import iconList from './iconListData'
|
|
23
24
|
|
|
24
25
|
type UseBiorhythmType = (dpKey: string, disabledFeature?: boolean) => [BiorhythmBean, SetBiorhythmType];
|
|
25
26
|
type SetBiorhythmType = (biorhythmObj: BiorhythmBean, pushFeature?: boolean) => Promise<Result<any>>;
|
|
@@ -401,3 +402,91 @@ function dto2Vo(remoteBiorhythmBean: RemoteBiorhythmBean): BiorhythmBean {
|
|
|
401
402
|
}),
|
|
402
403
|
}
|
|
403
404
|
}
|
|
405
|
+
|
|
406
|
+
export const replaceImg = (img) => {
|
|
407
|
+
const item = iconList?.find(val => val.id === Number(img))
|
|
408
|
+
switch (img) {
|
|
409
|
+
case 'rhythm_icon1':
|
|
410
|
+
case '31':
|
|
411
|
+
return { icon: res.biorhythom_icon1, iconId: 1 }
|
|
412
|
+
case 'rhythm_icon2':
|
|
413
|
+
case '33':
|
|
414
|
+
return { icon: res.biorhythom_icon5, iconId: 5 }
|
|
415
|
+
case 'rhythm_icon3':
|
|
416
|
+
case '35':
|
|
417
|
+
return { icon: res.biorhythom_icon2, iconId: 2 }
|
|
418
|
+
case 'rhythm_icon4':
|
|
419
|
+
case '32':
|
|
420
|
+
return { icon: res.biorhythom_icon9, iconId: 9 }
|
|
421
|
+
case 'rhythm_icon12':
|
|
422
|
+
case '39':
|
|
423
|
+
return { icon: res.biorhythom_icon3, iconId: 3 }
|
|
424
|
+
default:
|
|
425
|
+
return { icon: item?.icon, iconId: item?.id }
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function useStorageBiorhythm(): [[], (key, enable: boolean, gradient: BiorhythmGradientType, weeks: number[], planList: Plan[]) => void, (key) => void, (key) => {
|
|
430
|
+
weeks: number[];
|
|
431
|
+
planList: Plan[];
|
|
432
|
+
enable: boolean;
|
|
433
|
+
gradient: BiorhythmGradientType
|
|
434
|
+
}] {
|
|
435
|
+
const [storageBiorhythms, setStorageBiorhythms] = useState([])
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
AsyncStorage.getItem('BIORHYTHM_STORAGE').then(res => {
|
|
438
|
+
if (res) {
|
|
439
|
+
const storageBiorhythms = JSON.parse(res)
|
|
440
|
+
setStorageBiorhythms(storageBiorhythms)
|
|
441
|
+
xLog('AsyncStorage getBiorhythm', storageBiorhythms)
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
}, [])
|
|
445
|
+
|
|
446
|
+
const saveBiorhythm = useCallback((key, enable: boolean, gradient: BiorhythmGradientType, weeks: number[], planList: Plan[]) => {
|
|
447
|
+
const newPlanList = planList?.map(item => {
|
|
448
|
+
return { ...item, icon: `${item.icon}` }
|
|
449
|
+
}).sort((a, b) => a.time - b.time)
|
|
450
|
+
const biorhythm = cloneDeep({
|
|
451
|
+
key: key,
|
|
452
|
+
enable: enable,
|
|
453
|
+
weeks: weeks,
|
|
454
|
+
gradient: gradient,
|
|
455
|
+
planList: newPlanList,
|
|
456
|
+
})
|
|
457
|
+
const newStorageBiorhythms = storageBiorhythms.filter(item => item.key !== key)
|
|
458
|
+
newStorageBiorhythms.unshift(biorhythm)
|
|
459
|
+
setStorageBiorhythms(newStorageBiorhythms)
|
|
460
|
+
xLog('AsyncStorage saveBiorhythm', newStorageBiorhythms)
|
|
461
|
+
AsyncStorage.setItem('BIORHYTHM_STORAGE', JSON.stringify(newStorageBiorhythms), (error) => {
|
|
462
|
+
xLog('AsyncStorage saveBiorhythm error', error)
|
|
463
|
+
}).then()
|
|
464
|
+
}, [storageBiorhythms])
|
|
465
|
+
|
|
466
|
+
const removeBiorhythm = useCallback((key) => {
|
|
467
|
+
const newStorageBiorhythms = storageBiorhythms.filter(item => item.key !== key)
|
|
468
|
+
setStorageBiorhythms(newStorageBiorhythms)
|
|
469
|
+
AsyncStorage.setItem('BIORHYTHM_STORAGE', JSON.stringify(newStorageBiorhythms)).then(res => {
|
|
470
|
+
xLog('AsyncStorage removeBiorhythm res', res)
|
|
471
|
+
})
|
|
472
|
+
}, [storageBiorhythms])
|
|
473
|
+
|
|
474
|
+
const applyBiorhythm = useCallback((key) => {
|
|
475
|
+
const newBiorhythm = storageBiorhythms.find(item => item.key === key)
|
|
476
|
+
const planList = newBiorhythm.planList?.map((it, index) => {
|
|
477
|
+
return {
|
|
478
|
+
...it,
|
|
479
|
+
index: it.index ?? index,
|
|
480
|
+
icon: replaceImg(it?.iconId || it?.icon)?.icon,
|
|
481
|
+
iconId: replaceImg(it?.iconId || it?.icon)?.iconId,
|
|
482
|
+
}
|
|
483
|
+
})
|
|
484
|
+
return {
|
|
485
|
+
weeks: newBiorhythm.weeks,
|
|
486
|
+
planList: planList,
|
|
487
|
+
enable: newBiorhythm.enable,
|
|
488
|
+
gradient: newBiorhythm.gradient,
|
|
489
|
+
}
|
|
490
|
+
}, [storageBiorhythms])
|
|
491
|
+
return [storageBiorhythms, saveBiorhythm, removeBiorhythm, applyBiorhythm]
|
|
492
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Card from '@ledvance/base/src/components/Card'
|
|
2
2
|
import DeleteButton from '@ledvance/base/src/components/DeleteButton'
|
|
3
|
+
import InfoText from '@ledvance/base/src/components/InfoText'
|
|
3
4
|
import Page from '@ledvance/base/src/components/Page'
|
|
4
5
|
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
5
6
|
import LdvWeekView from '@ledvance/base/src/components/weekSelect'
|
|
@@ -8,17 +9,18 @@ import { useParams } from '@ledvance/base/src/hooks/Hooks'
|
|
|
8
9
|
import I18n from '@ledvance/base/src/i18n'
|
|
9
10
|
import { useDeviceInfo, useSystemTimeFormate, } from '@ledvance/base/src/models/modules/NativePropsSlice'
|
|
10
11
|
import res from '@ledvance/base/src/res'
|
|
12
|
+
import { xLog } from '@ledvance/base/src/utils'
|
|
11
13
|
import { cctToColor } from '@ledvance/base/src/utils/cctUtils'
|
|
12
|
-
import { convertMinutesTo12HourFormat, loopText, showDialog
|
|
14
|
+
import { convertMinutesTo12HourFormat, loopText, showDialog } from '@ledvance/base/src/utils/common'
|
|
13
15
|
import { useNavigation } from '@react-navigation/native'
|
|
14
16
|
import { useDebounceFn, useReactive, useUpdateEffect } from 'ahooks'
|
|
15
17
|
import { useConflictTask } from 'hooks/DeviceDpStateHooks'
|
|
16
18
|
import { cloneDeep, sortBy } from 'lodash'
|
|
17
|
-
import React, { useCallback, useMemo } from 'react'
|
|
18
|
-
import { FlatList, Image, Linking, ScrollView, Switch, Text, TouchableOpacity, View } from 'react-native'
|
|
19
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
20
|
+
import { FlatList, Image, Linking, ScrollView, Switch, Text, TouchableOpacity, View, AsyncStorage } from 'react-native'
|
|
19
21
|
import { Dialog, Modal, Utils } from 'tuya-panel-kit'
|
|
20
22
|
import { ui_biz_routerKey } from '../../navigation/Routers'
|
|
21
|
-
import { useBiorhythm } from './BiorhythmActions'
|
|
23
|
+
import { replaceImg, useBiorhythm, useStorageBiorhythm } from './BiorhythmActions'
|
|
22
24
|
import {
|
|
23
25
|
BiorhythmBean,
|
|
24
26
|
BiorhythmGradientType,
|
|
@@ -64,6 +66,8 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
64
66
|
const is24Hour = useSystemTimeFormate()
|
|
65
67
|
const devicesJudge = pIdList.some(val => val === productId)
|
|
66
68
|
const [checkConflict, resolveConflict] = useConflictTask(params.conflictDps)
|
|
69
|
+
const [storageBiorhythms, saveStorageBiorhythms, removeStorageBiorhythm, applyStorageBiorhythm] = useStorageBiorhythm()
|
|
70
|
+
const [biorhythmListVisible, setBiorhythmListVisible] = useState(false)
|
|
67
71
|
|
|
68
72
|
const state = useReactive<UIState>({
|
|
69
73
|
...biorhythm,
|
|
@@ -163,29 +167,6 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
163
167
|
run()
|
|
164
168
|
}, [state.flag])
|
|
165
169
|
|
|
166
|
-
const replaceImg = (img) => {
|
|
167
|
-
const item = iconList?.find(val => val.id === Number(img))
|
|
168
|
-
switch (img) {
|
|
169
|
-
case 'rhythm_icon1':
|
|
170
|
-
case '31':
|
|
171
|
-
return { icon: res.biorhythom_icon1, iconId: 1 }
|
|
172
|
-
case 'rhythm_icon2':
|
|
173
|
-
case '33':
|
|
174
|
-
return { icon: res.biorhythom_icon5, iconId: 5 }
|
|
175
|
-
case 'rhythm_icon3':
|
|
176
|
-
case '35':
|
|
177
|
-
return { icon: res.biorhythom_icon2, iconId: 2 }
|
|
178
|
-
case 'rhythm_icon4':
|
|
179
|
-
case '32':
|
|
180
|
-
return { icon: res.biorhythom_icon9, iconId: 9 }
|
|
181
|
-
case 'rhythm_icon12':
|
|
182
|
-
case '39':
|
|
183
|
-
return { icon: res.biorhythom_icon3, iconId: 3 }
|
|
184
|
-
default:
|
|
185
|
-
return { icon: item?.icon, iconId: item?.id }
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
170
|
useUpdateEffect(() => {
|
|
190
171
|
const planList = biorhythm.planList?.map(item => {
|
|
191
172
|
return {
|
|
@@ -261,6 +242,26 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
261
242
|
<Page
|
|
262
243
|
backText={deviceInfo.name}
|
|
263
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>}
|
|
264
265
|
headlineText={I18n.getLang('add_new_trigger_time_system_back_text')}
|
|
265
266
|
headlineIconContent={<Switch
|
|
266
267
|
value={state.enable}
|
|
@@ -275,7 +276,7 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
275
276
|
channel: 1
|
|
276
277
|
}
|
|
277
278
|
if (enable && checkConflict(biorhythmTask)) {
|
|
278
|
-
return
|
|
279
|
+
return showDialog({
|
|
279
280
|
method: 'confirm',
|
|
280
281
|
title: I18n.getLang('conflict_dialog_active_item_bio_rhythm_titel'),
|
|
281
282
|
subTitle: I18n.getLang('conflict_dialog_active_item_bio_rhythm_description'),
|
|
@@ -580,7 +581,7 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
580
581
|
// fontFamily: 'helvetica_neue_lt_std_roman',
|
|
581
582
|
}}
|
|
582
583
|
onPress={() => {
|
|
583
|
-
|
|
584
|
+
showDialog({
|
|
584
585
|
method: 'confirm',
|
|
585
586
|
title: I18n.getLang('bio_ryhthm_reset_description_text'),
|
|
586
587
|
onConfirm: (_, { close }) => {
|
|
@@ -645,6 +646,54 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
645
646
|
showGradientTypeSelectModal(false)
|
|
646
647
|
}}
|
|
647
648
|
/>
|
|
649
|
+
<Modal visible={biorhythmListVisible} onMaskPress={() => {setBiorhythmListVisible(false)}}>
|
|
650
|
+
<View style={{ height: cx(300), padding: cx(16), backgroundColor: props.theme?.card.background }}>
|
|
651
|
+
{
|
|
652
|
+
storageBiorhythms.length === 0 ? (
|
|
653
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
654
|
+
<InfoText
|
|
655
|
+
textStyle={{ flex: undefined }}
|
|
656
|
+
icon={res.ic_info}
|
|
657
|
+
text={I18n.getLang('energyconsumption_emptydata')}
|
|
658
|
+
/>
|
|
659
|
+
</View>
|
|
660
|
+
) : (
|
|
661
|
+
<FlatList
|
|
662
|
+
data={storageBiorhythms}
|
|
663
|
+
renderItem={({ item }) => {
|
|
664
|
+
return <View style={{ padding: cx(5), flexDirection: 'row' }}>
|
|
665
|
+
<Text style={{ flex: 1, color: props.theme?.global.fontColor }}>{item.key}</Text>
|
|
666
|
+
<TouchableOpacity style={{width: cx(24), height: cx(24), marginRight: cx(20)}} onPress={() => {
|
|
667
|
+
showDialog({
|
|
668
|
+
method: 'confirm',
|
|
669
|
+
title: I18n.getLang('biorhythm_delete_tips'),
|
|
670
|
+
onConfirm: (_, { close }) => {
|
|
671
|
+
removeStorageBiorhythm(item.key)
|
|
672
|
+
close()
|
|
673
|
+
}
|
|
674
|
+
})
|
|
675
|
+
}}>
|
|
676
|
+
<Image source={{ uri: res.delete}} style={{width: cx(24), height: cx(24), tintColor: props.theme?.global.warning}} />
|
|
677
|
+
</TouchableOpacity>
|
|
678
|
+
<TouchableOpacity style={{width: cx(24), height: cx(24), marginRight: cx(10)}} onPress={() => {
|
|
679
|
+
const newBiorhythm = applyStorageBiorhythm(item.key)
|
|
680
|
+
state.enable = newBiorhythm.enable
|
|
681
|
+
state.gradient = newBiorhythm.gradient
|
|
682
|
+
state.weeks = newBiorhythm.weeks
|
|
683
|
+
state.planList = newBiorhythm.planList
|
|
684
|
+
run()
|
|
685
|
+
setBiorhythmListVisible(false)
|
|
686
|
+
}}>
|
|
687
|
+
<Image source={{ uri:res.ic_checked}} style={{width: cx(24), height: cx(24), tintColor: props.theme?.icon.normal}} />
|
|
688
|
+
</TouchableOpacity>
|
|
689
|
+
</View>
|
|
690
|
+
}}
|
|
691
|
+
keyExtractor={(item) => `${item.key}`}
|
|
692
|
+
/>
|
|
693
|
+
)
|
|
694
|
+
}
|
|
695
|
+
</View>
|
|
696
|
+
</Modal>
|
|
648
697
|
</>
|
|
649
698
|
)
|
|
650
699
|
}
|
|
@@ -5,10 +5,57 @@ import React, { useRef } from 'react'
|
|
|
5
5
|
import { View } from 'react-native'
|
|
6
6
|
import { Utils } from 'tuya-panel-kit'
|
|
7
7
|
import { PowerDataItem } from '../EnergyConsumptionActions'
|
|
8
|
+
import dayjs from 'dayjs'
|
|
8
9
|
|
|
9
10
|
const { withTheme } = Utils.ThemeUtils
|
|
10
11
|
const cx = Utils.RatioUtils.convertX
|
|
11
12
|
|
|
13
|
+
/** 相邻两点时间差超过该阈值(ms)时插入补充点 */
|
|
14
|
+
const GAP_THRESHOLD_MS = 5 * 1000 // 5 秒
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 在时间间隔较大的相邻点之间插入一个"间隙补充点",模拟阶梯式折线效果。
|
|
18
|
+
*
|
|
19
|
+
* 插入规则(基于值的变化方向):
|
|
20
|
+
* - 值下降(cur > next):在 cur.time + 1s 处插入 next.value
|
|
21
|
+
* → 表示值在前点之后立刻跌落,后续一直保持新低值直到 next 点
|
|
22
|
+
* - 值上升(cur <= next):在 next.time - 1s 处插入 cur.value
|
|
23
|
+
* → 表示值一直保持旧值平稳,在 next 点前 1 秒才结束
|
|
24
|
+
*/
|
|
25
|
+
function fillGaps(items: PowerDataItem[]): PowerDataItem[] {
|
|
26
|
+
if (!items || items.length < 2) return items
|
|
27
|
+
const result: PowerDataItem[] = []
|
|
28
|
+
for (let i = 0; i < items.length; i++) {
|
|
29
|
+
result.push(items[i])
|
|
30
|
+
if (i < items.length - 1) {
|
|
31
|
+
const cur = items[i]
|
|
32
|
+
const next = items[i + 1]
|
|
33
|
+
const diffMs = next.time - cur.time
|
|
34
|
+
if (diffMs > GAP_THRESHOLD_MS) {
|
|
35
|
+
let gapTime: number
|
|
36
|
+
let gapValue: number
|
|
37
|
+
if (cur.value > next.value) {
|
|
38
|
+
// 值下降:立刻跌落 —— 在前点后 1 秒插入 next.value
|
|
39
|
+
gapTime = cur.time + 1000
|
|
40
|
+
gapValue = next.value
|
|
41
|
+
} else {
|
|
42
|
+
// 值上升(或相等):保持旧值 —— 在后点前 1 秒插入 cur.value
|
|
43
|
+
gapTime = next.time - 1000
|
|
44
|
+
gapValue = cur.value
|
|
45
|
+
}
|
|
46
|
+
const gapDayjs = dayjs(gapTime)
|
|
47
|
+
result.push({
|
|
48
|
+
key: gapDayjs.format('YYYY-MM-DD HH:mm:ss'),
|
|
49
|
+
chartTitle: gapDayjs.format('MM/DD/YYYY HH:mm:ss'),
|
|
50
|
+
time: gapTime,
|
|
51
|
+
value: gapValue,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
12
59
|
interface PowerLineChartProps {
|
|
13
60
|
theme?: ThemeType
|
|
14
61
|
data: PowerDataItem[],
|
|
@@ -19,7 +66,7 @@ const PowerLineChart = (props: PowerLineChartProps) => {
|
|
|
19
66
|
const echarts = useRef()
|
|
20
67
|
const { data, height } = props
|
|
21
68
|
|
|
22
|
-
const values = data?.map(item => ([item.time, item.value]))
|
|
69
|
+
const values = fillGaps(data)?.map(item => ([item.time, item.value]))
|
|
23
70
|
|
|
24
71
|
const option = {
|
|
25
72
|
tooltip: {
|
|
@@ -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
|
}
|
|
@@ -1200,12 +1177,6 @@ function getDefMixLightSceneList(): MixRemoteMoodInfo[] {
|
|
|
1200
1177
|
image: '',
|
|
1201
1178
|
value: '00030101020e0d0001f401f40000',
|
|
1202
1179
|
},
|
|
1203
|
-
{
|
|
1204
|
-
name: I18n.getLang('mesh_device_detail_lighting_color_mode'),
|
|
1205
|
-
image: '',
|
|
1206
|
-
value:
|
|
1207
|
-
'00040000010603464601000003e803e8464601007803e803e846460100f003e803e8464601003d03e803e846460100ae03e803e8464601011303e803e8',
|
|
1208
|
-
},
|
|
1209
1180
|
{
|
|
1210
1181
|
name: I18n.getLang('mesh_device_detail_lighting_white_mode'),
|
|
1211
1182
|
image: '',
|
|
@@ -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
|
|
10
|
-
import { Image,
|
|
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 {
|
|
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
|
|
42
|
-
|
|
43
|
-
), [MoodJumpGradientMode, deviceTypeOption]);
|
|
59
|
+
const isDarkMode = theme?.type === 'dark';
|
|
60
|
+
const defaultGreyGradient = isDarkMode ? ['#444444', '#333333'] : ['#E5E5E5', '#DCDCDC'];
|
|
44
61
|
|
|
45
|
-
const
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
</
|
|
99
|
+
</View>
|
|
83
100
|
);
|
|
84
|
-
};
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
<
|
|
152
|
-
|
|
153
|
-
<View style={
|
|
154
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -30,10 +30,19 @@ import I18n from '@ledvance/base/src/i18n';
|
|
|
30
30
|
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
const cx = Utils.RatioUtils
|
|
33
|
+
const { convertX: cx, width: screenWidth } = Utils.RatioUtils;
|
|
34
34
|
const { withTheme } = Utils.ThemeUtils
|
|
35
35
|
|
|
36
36
|
const MAX_MOOD_COUNT = 255;
|
|
37
|
+
// --- 动态计算项目宽度 ---
|
|
38
|
+
// 1. 定义网格的边距和列间距
|
|
39
|
+
const GRID_HORIZONTAL_PADDING = cx(24);
|
|
40
|
+
const GRID_GAP = cx(16);
|
|
41
|
+
const NUM_COLUMNS = 2;
|
|
42
|
+
// 2. 计算每个 MoodItem 的宽度
|
|
43
|
+
// (屏幕总宽度 - 两边的边距 - (列数 - 1) * 列间距) / 列数
|
|
44
|
+
const ITEM_WIDTH =
|
|
45
|
+
(screenWidth - GRID_HORIZONTAL_PADDING * 2 - (NUM_COLUMNS - 1) * GRID_GAP) / NUM_COLUMNS;
|
|
37
46
|
|
|
38
47
|
const MoodPage = (props: { theme?: ThemeType }) => {
|
|
39
48
|
const params = useParams<MoodPageParams>();
|
|
@@ -319,6 +328,13 @@ const MoodPage = (props: { theme?: ThemeType }) => {
|
|
|
319
328
|
alignItems: 'flex-start',
|
|
320
329
|
alignSelf: 'flex-start'
|
|
321
330
|
},
|
|
331
|
+
refresh: {
|
|
332
|
+
alignItems: 'flex-end',
|
|
333
|
+
paddingRight: cx(24)
|
|
334
|
+
},
|
|
335
|
+
columnWrapperStyle: {
|
|
336
|
+
justifyContent: 'space-between',
|
|
337
|
+
},
|
|
322
338
|
})
|
|
323
339
|
|
|
324
340
|
return (
|
|
@@ -353,18 +369,19 @@ const MoodPage = (props: { theme?: ThemeType }) => {
|
|
|
353
369
|
}}
|
|
354
370
|
/>
|
|
355
371
|
</View>}
|
|
356
|
-
<TouchableOpacity
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
+
}}
|
|
368
385
|
>
|
|
369
386
|
<Image source={{uri: res.ic_refresh}} style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }} />
|
|
370
387
|
</TouchableOpacity>
|
|
@@ -382,9 +399,21 @@ const MoodPage = (props: { theme?: ThemeType }) => {
|
|
|
382
399
|
)}
|
|
383
400
|
<FlatList
|
|
384
401
|
data={state.filterMoods}
|
|
402
|
+
// 关键属性 1: 设置列数
|
|
403
|
+
numColumns={NUM_COLUMNS}
|
|
404
|
+
// 关键属性 2: 为 FlatList 提供一个唯一的 key,当列数改变时强制刷新
|
|
405
|
+
key={NUM_COLUMNS}
|
|
406
|
+
// 关键属性 3: 设置整个列表容器的样式,主要是左右边距
|
|
407
|
+
contentContainerStyle={{
|
|
408
|
+
paddingHorizontal: GRID_HORIZONTAL_PADDING,
|
|
409
|
+
}}
|
|
410
|
+
// 关键属性 4: 设置行包装器的样式,用于在列之间创建间距
|
|
411
|
+
columnWrapperStyle={styles.columnWrapperStyle}
|
|
385
412
|
renderItem={({ item }) => {
|
|
386
413
|
return (
|
|
387
414
|
<MoodItem
|
|
415
|
+
// 关键修改:通过 style prop 传入计算好的宽度
|
|
416
|
+
style={{ width: ITEM_WIDTH }}
|
|
388
417
|
enable={getItemEnable(item)}
|
|
389
418
|
isMix={!!(params.isMixLight || params.isCeilingLight)}
|
|
390
419
|
mood={item}
|
|
@@ -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
|
|
10
|
+
import MixMoodColorsLine from './MixMoodColorsLine';
|
|
11
11
|
|
|
12
12
|
const cx = Utils.RatioUtils.convertX;
|
|
13
13
|
const { withTheme } = Utils.ThemeUtils;
|