@ledvance/ui-biz-bundle 1.1.156 → 1.1.158
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 +1 -1
- package/src/modules/flags/FlagItem.tsx +123 -59
- package/src/modules/flags/FlagPage.tsx +108 -85
- package/src/modules/history/HistoryPage.tsx +1 -0
- package/src/newModules/biorhythm/BiorhythmPage.tsx +11 -10
- package/src/newModules/energyConsumption/EnergyConsumptionChart/styles.ts +2 -1
- package/src/newModules/mood/AddMoodPage.tsx +66 -65
- package/src/newModules/mood/MoodItem.tsx +166 -117
- package/src/newModules/mood/RecommendMoodItem.tsx +81 -49
- package/src/newModules/overchargeSwitch/OverchargeSwitchPage.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,80 +1,144 @@
|
|
|
1
|
-
import { StyleSheet, View, Text, ViewStyle, Image, TouchableOpacity } from 'react-native'
|
|
2
|
-
import React from 'react'
|
|
3
1
|
import Card from '@ledvance/base/src/components/Card'
|
|
4
|
-
import { Utils } from 'tuya-panel-kit'
|
|
5
2
|
import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine'
|
|
6
3
|
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
7
4
|
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
8
5
|
import res from '@ledvance/base/src/res'
|
|
6
|
+
import React, { useMemo, useState } from 'react' // 重新引入 useState 和 LayoutChangeEvent
|
|
7
|
+
import { Image, LayoutChangeEvent, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'
|
|
8
|
+
import { Utils } from 'tuya-panel-kit'
|
|
9
9
|
|
|
10
|
-
const cx = Utils.RatioUtils
|
|
10
|
+
const { convertX: cx } = Utils.RatioUtils
|
|
11
11
|
const { withTheme } = Utils.ThemeUtils
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
const CONTENT_HORIZONTAL_MARGIN = cx(16)
|
|
14
|
+
|
|
15
|
+
interface FlagItemProps {
|
|
16
|
+
theme?: ThemeType;
|
|
17
|
+
enable: boolean;
|
|
18
|
+
title: string;
|
|
19
|
+
colors: string[];
|
|
20
|
+
icon?: number | string;
|
|
21
|
+
style?: ViewStyle;
|
|
22
|
+
containerWidth?: number; // 1. 将 containerWidth 变为可选属性
|
|
23
|
+
onPress?: () => void;
|
|
24
|
+
onSwitch: (enable: boolean) => void;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const getStyles = (theme?: ThemeType) => StyleSheet.create({
|
|
28
|
+
// ... 样式定义保持不变 ...
|
|
29
|
+
card: {
|
|
30
|
+
marginHorizontal: cx(24),
|
|
31
|
+
// 移除 Card 的水平内边距,以便我们能测量到完整的宽度
|
|
32
|
+
paddingHorizontal: 0,
|
|
33
|
+
},
|
|
34
|
+
headline: {
|
|
35
|
+
flexDirection: 'row',
|
|
36
|
+
marginHorizontal: CONTENT_HORIZONTAL_MARGIN,
|
|
37
|
+
justifyContent: 'space-between',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
},
|
|
40
|
+
headText: {
|
|
41
|
+
color: theme?.global.fontColor,
|
|
42
|
+
fontSize: cx(16),
|
|
43
|
+
lineHeight: cx(20),
|
|
44
|
+
flex: 1,
|
|
45
|
+
},
|
|
46
|
+
checkbox: {
|
|
47
|
+
width: cx(45),
|
|
48
|
+
height: cx(45),
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
alignItems: 'flex-end',
|
|
51
|
+
},
|
|
52
|
+
checkboxImage: {
|
|
53
|
+
width: cx(44),
|
|
54
|
+
height: cx(44),
|
|
55
|
+
},
|
|
56
|
+
lineContainer: {
|
|
57
|
+
marginHorizontal: CONTENT_HORIZONTAL_MARGIN,
|
|
58
|
+
minHeight: cx(24),
|
|
59
|
+
},
|
|
60
|
+
icon: {
|
|
61
|
+
width: cx(60),
|
|
62
|
+
aspectRatio: 2.14,
|
|
63
|
+
marginRight: cx(10),
|
|
64
|
+
},
|
|
65
|
+
titleContainer: {
|
|
66
|
+
flexDirection: 'row',
|
|
67
|
+
flex: 1,
|
|
68
|
+
paddingRight: cx(5),
|
|
69
|
+
justifyContent: 'flex-start',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
function FlagItem(props: FlagItemProps) {
|
|
75
|
+
const { theme, containerWidth, enable, title, colors, icon: iconProp, style, onPress, onSwitch } = props
|
|
76
|
+
const styles = useMemo(() => getStyles(theme), [theme])
|
|
77
|
+
const icon = typeof iconProp === 'number' ? iconProp : { uri: iconProp }
|
|
78
|
+
|
|
79
|
+
// 2. 引入 state 用于存储内部测量的宽度
|
|
80
|
+
const [selfMeasuredWidth, setSelfMeasuredWidth] = useState(0)
|
|
81
|
+
|
|
82
|
+
// 3. 创建 onLayout 回调,用于自我测量
|
|
83
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
84
|
+
// 如果父组件已经提供了宽度,则忽略内部测量,避免冲突
|
|
85
|
+
if (containerWidth && containerWidth > 0) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
const { width: cardWidth } = event.nativeEvent.layout
|
|
89
|
+
if (cardWidth > 0 && cardWidth !== selfMeasuredWidth) {
|
|
90
|
+
setSelfMeasuredWidth(cardWidth)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
26
93
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
checkbox: {
|
|
45
|
-
width: cx(45),
|
|
46
|
-
height: cx(45),
|
|
47
|
-
marginTop: cx(-5),
|
|
48
|
-
marginBottom: cx(-10),
|
|
49
|
-
},
|
|
50
|
-
gradientItem: {
|
|
51
|
-
alignItems: 'center',
|
|
52
|
-
},
|
|
53
|
-
})
|
|
94
|
+
// 4. 决定最终使用的宽度
|
|
95
|
+
const finalLineWidth = useMemo(() => {
|
|
96
|
+
// 优先使用父组件传入的宽度(适用于 FlatList)
|
|
97
|
+
if (containerWidth && containerWidth > 0) {
|
|
98
|
+
// 这里的计算需要注意:父组件传来的 containerWidth 是整个屏幕的宽度
|
|
99
|
+
// 所以需要减去 Card 的外边距
|
|
100
|
+
const cardOuterMargin = cx(24)
|
|
101
|
+
return containerWidth - (cardOuterMargin * 2) - (CONTENT_HORIZONTAL_MARGIN * 2)
|
|
102
|
+
}
|
|
103
|
+
// 其次使用自我测量的宽度(适用于普通 View)
|
|
104
|
+
if (selfMeasuredWidth > 0) {
|
|
105
|
+
// selfMeasuredWidth 是 Card 的宽度,所以只需减去内容的内边距
|
|
106
|
+
return selfMeasuredWidth - (CONTENT_HORIZONTAL_MARGIN * 2)
|
|
107
|
+
}
|
|
108
|
+
// 如果都没有,则返回 0
|
|
109
|
+
return 0
|
|
110
|
+
}, [containerWidth, selfMeasuredWidth])
|
|
54
111
|
|
|
55
112
|
return (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<Spacer height={cx(16)} />
|
|
113
|
+
// 5. 将 onLayout 绑定到 Card 上,让组件能自我测量
|
|
114
|
+
<Card style={[styles.card, style]} onPress={onPress} onLayout={handleLayout}>
|
|
115
|
+
<View accessibilityLabel={'FlagItem'} accessibilityHint={`${title}`}>
|
|
116
|
+
<Spacer height={cx(8)}/>
|
|
61
117
|
<View style={styles.headline}>
|
|
62
|
-
<View style={
|
|
63
|
-
{
|
|
64
|
-
{
|
|
118
|
+
<View style={styles.titleContainer}>
|
|
119
|
+
{title ? <Text style={styles.headText}>{title}</Text> : null}
|
|
120
|
+
{iconProp ? <Image source={icon} style={styles.icon}/> : null}
|
|
65
121
|
</View>
|
|
66
|
-
<TouchableOpacity style={styles.checkbox} onPress={() =>
|
|
67
|
-
<Image
|
|
122
|
+
<TouchableOpacity style={styles.checkbox} onPress={() => onSwitch(!enable)}>
|
|
123
|
+
<Image
|
|
124
|
+
source={{ uri: res.ic_check }}
|
|
125
|
+
style={[styles.checkboxImage, { tintColor: enable ? theme?.icon.primary : theme?.icon.disable }]}
|
|
126
|
+
/>
|
|
68
127
|
</TouchableOpacity>
|
|
69
128
|
</View>
|
|
70
|
-
<Spacer
|
|
71
|
-
<View style={styles.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
129
|
+
<Spacer/>
|
|
130
|
+
<View style={styles.lineContainer}>
|
|
131
|
+
{/* 6. 使用最终计算出的宽度进行渲染 */}
|
|
132
|
+
{finalLineWidth > 0 && (
|
|
133
|
+
<MoodColorsLine
|
|
134
|
+
type={'separate'}
|
|
135
|
+
width={finalLineWidth}
|
|
136
|
+
nodeStyle={{ borderColor: theme?.card.border, borderWidth: 1 }}
|
|
137
|
+
colors={colors}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
76
140
|
</View>
|
|
77
|
-
<Spacer height={cx(16)}
|
|
141
|
+
<Spacer height={cx(16)}/>
|
|
78
142
|
</View>
|
|
79
143
|
</Card>
|
|
80
144
|
)
|
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import Page from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import Spacer from "@ledvance/base/src/components/Spacer";
|
|
6
|
-
import { Utils } from "tuya-panel-kit";
|
|
7
|
-
import FlagItem from "./FlagItem";
|
|
8
|
-
import { FlagUiInfo } from "./FlagInfo";
|
|
9
|
-
import { getRemoteFlag, saveFlag, saveFlagMode, useFlag } from "./FlagActions";
|
|
10
|
-
import { useRoute, useNavigation } from '@react-navigation/core'
|
|
11
|
-
import I18n from "@ledvance/base/src/i18n";
|
|
12
|
-
import { useReactive, useUpdateEffect } from "ahooks";
|
|
13
|
-
import { cloneDeep, difference, isEqual, last, map, range } from "lodash";
|
|
14
|
-
import { ui_biz_routerKey } from "../../navigation/Routers";
|
|
15
|
-
import res from "@ledvance/base/src/res";
|
|
16
|
-
import { hsv2Hex } from "@ledvance/base/src/utils";
|
|
17
|
-
import { SceneNodeTransitionMode } from "@ledvance/ui-biz-bundle/src/modules/scene/SceneInfo";
|
|
18
|
-
import { Result } from "@ledvance/base/src/models/modules/Result";
|
|
19
|
-
import { useDps } from "@ledvance/base/src/models/modules/NativePropsSlice";
|
|
20
|
-
import { ColorUtils, WORK_MODE } from "@tuya/tuya-panel-lamp-sdk/lib/utils";
|
|
21
|
-
import TextField from "@ledvance/base/src/components/TextField";
|
|
22
|
-
import { showDialog } from "@ledvance/base/src/utils/common";
|
|
23
|
-
import { defFlagHash, fnv1aHash } from "./FlagHash";
|
|
24
|
-
import { NativeApi } from "@ledvance/base/src/api/native";
|
|
1
|
+
import { NativeApi } from '@ledvance/base/src/api/native'
|
|
2
|
+
import Page from '@ledvance/base/src/components/Page'
|
|
3
|
+
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
4
|
+
import TextField from '@ledvance/base/src/components/TextField'
|
|
25
5
|
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
6
|
+
import I18n from '@ledvance/base/src/i18n'
|
|
7
|
+
import {
|
|
8
|
+
useDeviceId,
|
|
9
|
+
useDeviceInfo,
|
|
10
|
+
useDps,
|
|
11
|
+
useFlagMode,
|
|
12
|
+
useFlags
|
|
13
|
+
} from '@ledvance/base/src/models/modules/NativePropsSlice'
|
|
14
|
+
import { Result } from '@ledvance/base/src/models/modules/Result'
|
|
15
|
+
import res from '@ledvance/base/src/res'
|
|
16
|
+
import { hsv2Hex } from '@ledvance/base/src/utils'
|
|
17
|
+
import { showDialog } from '@ledvance/base/src/utils/common'
|
|
18
|
+
import { useNavigation, useRoute } from '@react-navigation/core'
|
|
19
|
+
import { ColorUtils, WORK_MODE } from '@tuya/tuya-panel-lamp-sdk/lib/utils'
|
|
20
|
+
import { useReactive, useUpdateEffect } from 'ahooks'
|
|
21
|
+
import { cloneDeep, difference, isEqual, last, map, range } from 'lodash'
|
|
22
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
23
|
+
import { FlatList, Image, TouchableOpacity, View } from 'react-native'
|
|
24
|
+
import { Utils } from 'tuya-panel-kit'
|
|
25
|
+
import { ui_biz_routerKey } from '../../navigation/Routers'
|
|
26
|
+
import { SceneNodeTransitionMode } from '../scene/SceneInfo'
|
|
27
|
+
import { getRemoteFlag, saveFlag, saveFlagMode, useFlag } from './FlagActions'
|
|
28
|
+
import { defFlagHash, fnv1aHash } from './FlagHash'
|
|
29
|
+
import { FlagUiInfo } from './FlagInfo'
|
|
30
|
+
import FlagItem from './FlagItem'
|
|
26
31
|
|
|
27
32
|
const cx = Utils.RatioUtils.convertX
|
|
28
33
|
const { withTheme } = Utils.ThemeUtils
|
|
@@ -100,6 +105,16 @@ const FlagPage = (props: { theme?: ThemeType }) => {
|
|
|
100
105
|
state.filterFlags = state.searchText !== '' ? cloneDeep(state.flags).filter(flag => (flag.name ?? '').toLowerCase().includes(state.searchText.toLowerCase())) : cloneDeep(state.flags)
|
|
101
106
|
}, [state.searchText, state.flags])
|
|
102
107
|
|
|
108
|
+
// 3. 在父组件中创建 state 存储容器宽度
|
|
109
|
+
const [listContainerWidth, setListContainerWidth] = useState(0)
|
|
110
|
+
// 4. 创建 onLayout 回调函数
|
|
111
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
112
|
+
const { width } = event.nativeEvent.layout
|
|
113
|
+
if (width > 0 && width !== listContainerWidth) {
|
|
114
|
+
setListContainerWidth(width)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
const getRemoteFlagInfo = async (isRefresh?: boolean) => {
|
|
104
119
|
const res = await getRemoteFlag(devInfo.devId, isRefresh)
|
|
105
120
|
if (res.success) {
|
|
@@ -175,7 +190,7 @@ const FlagPage = (props: { theme?: ThemeType }) => {
|
|
|
175
190
|
})
|
|
176
191
|
}
|
|
177
192
|
|
|
178
|
-
const getItemEnable = useCallback((flagItem: FlagUiInfo) =>{
|
|
193
|
+
const getItemEnable = useCallback((flagItem: FlagUiInfo) => {
|
|
179
194
|
const workMode = params.isCeilingLight ? WORK_MODE.WHITE : params.isStripLight ? WORK_MODE.COLOUR : WORK_MODE.SCENE
|
|
180
195
|
const idEnable = params.isSolarLight ? (flagMode?.flagId === flagItem.id) : (flagId === flagItem.id)
|
|
181
196
|
return flagMode?.flagMode && dps[params.workModeCode] === workMode && idEnable && dps[params.switchLedCode]
|
|
@@ -198,67 +213,75 @@ const FlagPage = (props: { theme?: ThemeType }) => {
|
|
|
198
213
|
}
|
|
199
214
|
}}
|
|
200
215
|
>
|
|
201
|
-
<View style={{
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
close
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
216
|
+
<View style={{ flex: 1 }} onLayout={handleLayout}>
|
|
217
|
+
<View style={{ flexDirection: 'row', display: 'flex', alignItems: 'center' }}>
|
|
218
|
+
<TextField
|
|
219
|
+
value={state.searchText}
|
|
220
|
+
onChangeText={text => {
|
|
221
|
+
state.searchText = text
|
|
222
|
+
}}
|
|
223
|
+
placeholder={I18n.getLang('country_selection_textfield_headline_search')}
|
|
224
|
+
style={{ marginHorizontal: cx(24), flex: 1 }}
|
|
225
|
+
/>
|
|
226
|
+
<TouchableOpacity
|
|
227
|
+
style={{ paddingRight: cx(24) }}
|
|
228
|
+
accessibilityLabel={'RefreshButton'}
|
|
229
|
+
accessibilityHint={'RefreshButton'}
|
|
230
|
+
onPress={() => {
|
|
231
|
+
showDialog({
|
|
232
|
+
method: 'confirm',
|
|
233
|
+
title: I18n.getLang('flag_resetbutton'),
|
|
234
|
+
subTitle: I18n.getLang('flag_resetdescription'),
|
|
235
|
+
onConfirm: async (_, { close }) => {
|
|
236
|
+
close()
|
|
237
|
+
state.loading = true
|
|
238
|
+
await getRemoteFlagInfo(true)
|
|
239
|
+
state.loading = false
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<Image source={{ uri: res.ic_refresh }}
|
|
245
|
+
style={{ width: cx(24), height: cx(24), tintColor: props.theme?.global.fontColor }}/>
|
|
246
|
+
</TouchableOpacity>
|
|
247
|
+
</View>
|
|
248
|
+
{/* 6. 只有在获取到有效宽度后才渲染 FlatList */}
|
|
249
|
+
{listContainerWidth > 0 && (
|
|
250
|
+
<FlatList
|
|
251
|
+
data={state.filterFlags}
|
|
252
|
+
renderItem={({ item }) => <FlagItem
|
|
253
|
+
enable={getItemEnable(item)}
|
|
254
|
+
title={item.name}
|
|
255
|
+
icon={item.icon}
|
|
256
|
+
colors={item.colors.map(item => hsv2Hex(item.h, item.s, item.v)).reverse()}
|
|
257
|
+
// 7. 【关键】将测量到的宽度传递给子组件
|
|
258
|
+
containerWidth={listContainerWidth}
|
|
259
|
+
onSwitch={async (enable) => {
|
|
260
|
+
if (enable) {
|
|
261
|
+
state.loading = true
|
|
262
|
+
await setFlag(item)
|
|
263
|
+
const flagHash = fnv1aHash(JSON.stringify(item.colors))
|
|
264
|
+
if (defFlagHash[flagHash]) {
|
|
265
|
+
NativeApi.putFlagFirebase({
|
|
266
|
+
country_flag: defFlagHash[flagHash].EN,
|
|
267
|
+
id: defFlagHash[flagHash].id
|
|
268
|
+
}).then()
|
|
269
|
+
}
|
|
270
|
+
state.loading = false
|
|
271
|
+
updateFlagMode(true, item)
|
|
272
|
+
}
|
|
273
|
+
}}
|
|
274
|
+
onPress={() => {
|
|
275
|
+
navigationRoute('edit', item)
|
|
276
|
+
}}
|
|
277
|
+
/>}
|
|
278
|
+
keyExtractor={item => item.name}
|
|
279
|
+
ListHeaderComponent={() => (<Spacer height={cx(10)}/>)}
|
|
280
|
+
ItemSeparatorComponent={() => (<Spacer/>)}
|
|
281
|
+
ListFooterComponent={() => (<Spacer/>)}
|
|
282
|
+
/>
|
|
283
|
+
)}
|
|
230
284
|
</View>
|
|
231
|
-
<FlatList
|
|
232
|
-
data={state.filterFlags}
|
|
233
|
-
renderItem={({ item }) => <FlagItem
|
|
234
|
-
enable={getItemEnable(item)}
|
|
235
|
-
title={item.name}
|
|
236
|
-
icon={item.icon}
|
|
237
|
-
colors={item.colors.map(item => hsv2Hex(item.h, item.s, item.v)).reverse()}
|
|
238
|
-
onSwitch={async (enable) => {
|
|
239
|
-
if (enable) {
|
|
240
|
-
state.loading = true
|
|
241
|
-
await setFlag(item)
|
|
242
|
-
const flagHash = fnv1aHash(JSON.stringify(item.colors))
|
|
243
|
-
if (defFlagHash[flagHash]) {
|
|
244
|
-
NativeApi.putFlagFirebase({
|
|
245
|
-
country_flag: defFlagHash[flagHash].EN,
|
|
246
|
-
id: defFlagHash[flagHash].id
|
|
247
|
-
}).then()
|
|
248
|
-
}
|
|
249
|
-
state.loading = false
|
|
250
|
-
updateFlagMode(true, item)
|
|
251
|
-
}
|
|
252
|
-
}}
|
|
253
|
-
onPress={() => {
|
|
254
|
-
navigationRoute('edit', item)
|
|
255
|
-
}}
|
|
256
|
-
/>}
|
|
257
|
-
keyExtractor={item => item.name}
|
|
258
|
-
ListHeaderComponent={() => (<Spacer height={cx(10)} />)}
|
|
259
|
-
ItemSeparatorComponent={() => (<Spacer />)}
|
|
260
|
-
ListFooterComponent={() => (<Spacer />)}
|
|
261
|
-
/>
|
|
262
285
|
</Page>
|
|
263
286
|
)
|
|
264
287
|
}
|
|
@@ -187,6 +187,7 @@ const SwitchHistoryPage = (props: { theme?: ThemeType }) => {
|
|
|
187
187
|
backText={backText || deviceInfo.name}
|
|
188
188
|
headlineText={headlineText || I18n.getLang('history_socket_headline_text')}
|
|
189
189
|
headlineIcon={res.download_icon}
|
|
190
|
+
accessibilityHint={I18n.getLang('history_socket_headline_text')}
|
|
190
191
|
onHeadlineIconClick={downFile}
|
|
191
192
|
info={<FeatureInfo title={I18n.getLang('history_socket_headline_text')} content={I18n.getLang('infobutton_history')} />}
|
|
192
193
|
>
|
|
@@ -553,16 +553,17 @@ const BiorhythmPage = (props: { theme?: ThemeType }) => {
|
|
|
553
553
|
{setTimer(item?.name)}
|
|
554
554
|
</Text>
|
|
555
555
|
</View>
|
|
556
|
-
<View
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
556
|
+
<View style={{ width: '100%', paddingHorizontal: cx(20), }}>
|
|
557
|
+
<View
|
|
558
|
+
style={{
|
|
559
|
+
width: '100%',
|
|
560
|
+
height: cx(24),
|
|
561
|
+
backgroundColor: bgColor,
|
|
562
|
+
marginTop: cx(5),
|
|
563
|
+
marginBottom: cx(24),
|
|
564
|
+
borderRadius: cx(8),
|
|
565
|
+
}}/>
|
|
566
|
+
</View>
|
|
566
567
|
</View>
|
|
567
568
|
</Card>
|
|
568
569
|
)
|
|
@@ -21,7 +21,8 @@ export const getStyles = (theme: ThemeType | undefined) => StyleSheet.create({
|
|
|
21
21
|
justifyContent: 'flex-end',
|
|
22
22
|
// marginHorizontal: cx(24),
|
|
23
23
|
marginTop: cx(15),
|
|
24
|
-
marginBottom: cx(-15)
|
|
24
|
+
marginBottom: cx(-15),
|
|
25
|
+
zIndex: 9999
|
|
25
26
|
},
|
|
26
27
|
downloadIcon: {
|
|
27
28
|
width: cx(24),
|
|
@@ -1,32 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import React, { useCallback } from 'react';
|
|
4
|
-
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
|
5
|
-
import { Utils } from 'tuya-panel-kit';
|
|
6
|
-
import RecommendMoodItem from './RecommendMoodItem';
|
|
7
|
-
import Spacer from '@ledvance/base/src/components/Spacer';
|
|
8
|
-
import { useReactive } from 'ahooks';
|
|
9
|
-
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
10
|
-
import {
|
|
11
|
-
MoodPageParams,
|
|
12
|
-
MoodNodeTransitionMode,
|
|
13
|
-
MoodUIInfo,
|
|
14
|
-
MoodJumpGradientMode,
|
|
15
|
-
} from './Interface';
|
|
16
|
-
import { Result } from '@ledvance/base/src/models/modules/Result';
|
|
17
|
-
import { ui_biz_routerKey } from '../../navigation/Routers'
|
|
18
|
-
import { difference, head, range } from 'lodash';
|
|
19
|
-
import { RecommendMood, getRecommendMixMoods, getRecommendMoods } from './MoodInfo';
|
|
1
|
+
import Page from '@ledvance/base/src/components/Page'
|
|
2
|
+
import Spacer from '@ledvance/base/src/components/Spacer'
|
|
20
3
|
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
21
|
-
|
|
22
|
-
|
|
4
|
+
import Strings from '@ledvance/base/src/i18n'
|
|
5
|
+
import { Result } from '@ledvance/base/src/models/modules/Result'
|
|
6
|
+
import { useNavigation, useRoute } from '@react-navigation/native'
|
|
7
|
+
import { useReactive } from 'ahooks'
|
|
8
|
+
import { difference, head, range } from 'lodash'
|
|
9
|
+
import React, { useCallback, useState } from 'react'
|
|
10
|
+
import { FlatList, LayoutChangeEvent, StyleSheet, Text, View } from 'react-native'
|
|
11
|
+
import { Utils } from 'tuya-panel-kit'
|
|
12
|
+
import { ui_biz_routerKey } from '../../navigation/Routers'
|
|
13
|
+
import { MoodJumpGradientMode, MoodNodeTransitionMode, MoodPageParams, MoodUIInfo, } from './Interface'
|
|
14
|
+
import { getRecommendMixMoods, getRecommendMoods, RecommendMood } from './MoodInfo'
|
|
15
|
+
import RecommendMoodItem from './RecommendMoodItem'
|
|
16
|
+
import {xLog} from "@ledvance/base/src/utils"
|
|
17
|
+
const cx = Utils.RatioUtils.convertX
|
|
23
18
|
const { withTheme } = Utils.ThemeUtils
|
|
24
19
|
|
|
25
20
|
export interface AddMoodPageParams {
|
|
26
21
|
isStatic: boolean;
|
|
27
22
|
moodIds: number[];
|
|
28
23
|
moduleParams: MoodPageParams;
|
|
29
|
-
nameRepeat: (mood: MoodUIInfo) => boolean
|
|
24
|
+
nameRepeat: (mood: MoodUIInfo) => boolean;
|
|
30
25
|
modDeleteMood: (mode: 'add' | 'edit' | 'del', currentMood: MoodUIInfo) => Promise<Result<any>>;
|
|
31
26
|
}
|
|
32
27
|
|
|
@@ -35,16 +30,23 @@ interface AddMoodPageState {
|
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
const AddMoodPage = (props: { theme?: ThemeType }) => {
|
|
38
|
-
const navigation = useNavigation()
|
|
39
|
-
const routeParams = useRoute().params as AddMoodPageParams
|
|
40
|
-
const moduleParams = routeParams.moduleParams
|
|
41
|
-
const isMix = !!(moduleParams.isMixLight || moduleParams.isCeilingLight)
|
|
33
|
+
const navigation = useNavigation()
|
|
34
|
+
const routeParams = useRoute().params as AddMoodPageParams
|
|
35
|
+
const moduleParams = routeParams.moduleParams
|
|
36
|
+
const isMix = !!(moduleParams.isMixLight || moduleParams.isCeilingLight)
|
|
37
|
+
xLog('isMix', isMix, moduleParams.isMixLight, moduleParams.isCeilingLight)
|
|
42
38
|
const state = useReactive<AddMoodPageState>({
|
|
43
39
|
data: isMix
|
|
44
40
|
? getRecommendMixMoods(routeParams.isStatic, moduleParams)
|
|
45
41
|
: getRecommendMoods(routeParams.isStatic, moduleParams),
|
|
46
|
-
})
|
|
47
|
-
|
|
42
|
+
})
|
|
43
|
+
const [containerWidth, setContainerWidth] = useState(0)
|
|
44
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
45
|
+
const { width } = event.nativeEvent.layout
|
|
46
|
+
if (width > 0 && width !== containerWidth) {
|
|
47
|
+
setContainerWidth(width)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
48
50
|
const getFormateItem = item => {
|
|
49
51
|
return {
|
|
50
52
|
...item,
|
|
@@ -54,32 +56,34 @@ const AddMoodPage = (props: { theme?: ThemeType }) => {
|
|
|
54
56
|
mode: 0,
|
|
55
57
|
speed: 75,
|
|
56
58
|
},
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
59
|
+
}
|
|
60
|
+
}
|
|
60
61
|
const onMoodItemClick = useCallback(
|
|
61
62
|
(moodItem: RecommendMood) => {
|
|
62
|
-
const idRange = range(0, 256)
|
|
63
|
-
const mainId: number = head(difference(idRange, routeParams.moodIds)) || 0
|
|
63
|
+
const idRange = range(0, 256)
|
|
64
|
+
const mainId: number = head(difference(idRange, routeParams.moodIds)) || 0
|
|
64
65
|
const secondaryId: number = moduleParams.isCeilingLight ? head(difference(idRange, [...routeParams.moodIds, mainId])) || 0 : 0
|
|
65
66
|
const url = routeParams.isStatic
|
|
66
67
|
? ui_biz_routerKey.ui_biz_static_mood_edit
|
|
67
68
|
: !!(moduleParams.isCeilingLight || moduleParams.isMixLight)
|
|
68
69
|
? ui_biz_routerKey.ui_biz_dynamic_mix_mood_edit
|
|
69
|
-
: ui_biz_routerKey.ui_biz_dynamic_mood_edit
|
|
70
|
+
: ui_biz_routerKey.ui_biz_dynamic_mood_edit
|
|
70
71
|
const currentMood = moodItem.mainLamp
|
|
71
|
-
? {
|
|
72
|
-
|
|
72
|
+
? {
|
|
73
|
+
...moodItem,
|
|
74
|
+
id: mainId,
|
|
75
|
+
mainLamp: { ...moodItem.mainLamp, id: mainId },
|
|
76
|
+
secondaryLamp: { ...moodItem.secondaryLamp, id: moodItem.secondaryLamp?.nodes?.length ? secondaryId : -1 }
|
|
77
|
+
}
|
|
78
|
+
: newMood(mainId, secondaryId, moduleParams.isSupportColor, routeParams.isStatic, moduleParams)
|
|
73
79
|
navigation.navigate(url, {
|
|
74
80
|
...routeParams,
|
|
75
81
|
mode: 'add',
|
|
76
82
|
currentMood,
|
|
77
|
-
})
|
|
83
|
+
})
|
|
78
84
|
},
|
|
79
85
|
[routeParams]
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
)
|
|
83
87
|
const styles = StyleSheet.create({
|
|
84
88
|
root: {
|
|
85
89
|
flex: 1,
|
|
@@ -92,8 +96,6 @@ const AddMoodPage = (props: { theme?: ThemeType }) => {
|
|
|
92
96
|
marginTop: cx(12),
|
|
93
97
|
},
|
|
94
98
|
})
|
|
95
|
-
|
|
96
|
-
|
|
97
99
|
return (
|
|
98
100
|
<Page
|
|
99
101
|
backText={Strings.getLang('add_new_static_mood_system_back')}
|
|
@@ -103,7 +105,7 @@ const AddMoodPage = (props: { theme?: ThemeType }) => {
|
|
|
103
105
|
: 'add_new_dynamic_mood_headline_text'
|
|
104
106
|
)}
|
|
105
107
|
>
|
|
106
|
-
<View style={styles.root}>
|
|
108
|
+
<View style={styles.root} onLayout={handleLayout}>
|
|
107
109
|
<Text style={styles.desc}>
|
|
108
110
|
{Strings.getLang(
|
|
109
111
|
routeParams.isStatic
|
|
@@ -111,32 +113,31 @@ const AddMoodPage = (props: { theme?: ThemeType }) => {
|
|
|
111
113
|
: 'add_new_dynamic_mood_description_text'
|
|
112
114
|
)}
|
|
113
115
|
</Text>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
{containerWidth > 0 && (
|
|
117
|
+
<FlatList
|
|
118
|
+
style={{ flex: 1 }}
|
|
119
|
+
data={state.data}
|
|
120
|
+
renderItem={({ item }) => (
|
|
119
121
|
<RecommendMoodItem
|
|
120
122
|
title={item.name}
|
|
121
123
|
isMix={isMix}
|
|
122
|
-
mood={
|
|
124
|
+
mood={item}
|
|
123
125
|
deviceTypeOption={moduleParams}
|
|
124
|
-
onPress={() =>
|
|
125
|
-
|
|
126
|
-
}
|
|
126
|
+
onPress={() => onMoodItemClick(item)}
|
|
127
|
+
// 5. 将测量到的宽度传递给子组件
|
|
128
|
+
containerWidth={containerWidth}
|
|
127
129
|
/>
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
)}
|
|
131
|
+
ItemSeparatorComponent={() => <Spacer/>}
|
|
132
|
+
ListHeaderComponent={() => <Spacer/>}
|
|
133
|
+
ListFooterComponent={() => <Spacer/>}
|
|
134
|
+
keyExtractor={item => item.name}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
135
137
|
</View>
|
|
136
138
|
</Page>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
139
|
+
)
|
|
140
|
+
}
|
|
140
141
|
export default withTheme(AddMoodPage)
|
|
141
142
|
|
|
142
143
|
const defStripConfig = {
|
|
@@ -161,7 +162,7 @@ function newMood(
|
|
|
161
162
|
? MoodJumpGradientMode.StringJump
|
|
162
163
|
: moduleParams.isStripLight
|
|
163
164
|
? MoodJumpGradientMode.StripJump
|
|
164
|
-
: MoodJumpGradientMode.SourceJump
|
|
165
|
+
: MoodJumpGradientMode.SourceJump
|
|
165
166
|
const node = {
|
|
166
167
|
brightness: 100,
|
|
167
168
|
colorTemp: 100,
|
|
@@ -169,7 +170,7 @@ function newMood(
|
|
|
169
170
|
s: 100,
|
|
170
171
|
v: 100,
|
|
171
172
|
isColorNode: !!(moduleParams.isCeilingLight || moduleParams.isMixLight) ? false : isColorMode,
|
|
172
|
-
}
|
|
173
|
+
}
|
|
173
174
|
return {
|
|
174
175
|
version: 0,
|
|
175
176
|
name: '',
|
|
@@ -194,5 +195,5 @@ function newMood(
|
|
|
194
195
|
version: 1,
|
|
195
196
|
nodes: isStatic ? [] : [{ ...node, isColorNode: true }, { ...node, isColorNode: true }],
|
|
196
197
|
},
|
|
197
|
-
}
|
|
198
|
+
}
|
|
198
199
|
}
|
|
@@ -1,171 +1,220 @@
|
|
|
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
1
|
import Card from '@ledvance/base/src/components/Card';
|
|
8
|
-
import Spacer from '@ledvance/base/src/components/Spacer';
|
|
9
2
|
import MoodColorsLine from '@ledvance/base/src/components/MoodColorsLine';
|
|
10
|
-
import
|
|
3
|
+
import Spacer from '@ledvance/base/src/components/Spacer';
|
|
4
|
+
import ThemeType from '@ledvance/base/src/config/themeType';
|
|
11
5
|
import I18n from '@ledvance/base/src/i18n';
|
|
12
6
|
import res from '@ledvance/base/src/res';
|
|
13
|
-
import
|
|
7
|
+
import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils';
|
|
8
|
+
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';
|
|
11
|
+
import { Utils } from 'tuya-panel-kit';
|
|
12
|
+
import { MoodJumpGradientMode, MoodLampInfo, MoodUIInfo } from './Interface';
|
|
14
13
|
|
|
15
14
|
const cx = Utils.RatioUtils.convertX;
|
|
16
|
-
const { withTheme } = Utils.ThemeUtils
|
|
15
|
+
const { withTheme } = Utils.ThemeUtils;
|
|
17
16
|
|
|
18
17
|
interface LightCategory {
|
|
19
|
-
isMixLight?: boolean
|
|
20
|
-
isStripLight?: boolean
|
|
21
|
-
isStringLight?: boolean
|
|
22
|
-
isCeilingLight?: boolean
|
|
18
|
+
isMixLight?: boolean;
|
|
19
|
+
isStripLight?: boolean;
|
|
20
|
+
isStringLight?: boolean;
|
|
21
|
+
isCeilingLight?: boolean;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
interface MoodItemProps extends ViewProps {
|
|
26
|
-
theme?: ThemeType
|
|
25
|
+
theme?: ThemeType;
|
|
27
26
|
enable: boolean;
|
|
28
27
|
isMix: boolean;
|
|
29
28
|
mood: MoodUIInfo;
|
|
30
29
|
style?: ViewStyle;
|
|
31
|
-
deviceTypeOption?: LightCategory
|
|
30
|
+
deviceTypeOption?: LightCategory;
|
|
32
31
|
onPress?: () => void;
|
|
33
32
|
onSwitch: (enable: boolean) => void;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
const MoodItem = (props: MoodItemProps) => {
|
|
37
|
-
const { mood, isMix, deviceTypeOption } = props;
|
|
36
|
+
const { mood, isMix, deviceTypeOption, theme } = props;
|
|
38
37
|
const isDynamic = useMemo(() => {
|
|
39
38
|
return mood.mainLamp.nodes?.length > 1 || mood.secondaryLamp.nodes?.length > 1;
|
|
40
39
|
}, [mood.mainLamp.nodes, mood.secondaryLamp.nodes]);
|
|
41
40
|
|
|
42
41
|
const gradientMode = useMemo(() => (
|
|
43
42
|
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
|
-
},
|
|
69
|
-
moodTypeItem: {
|
|
70
|
-
flexDirection: 'row',
|
|
71
|
-
},
|
|
72
|
-
moodTypeLabel: {
|
|
73
|
-
marginStart: cx(16),
|
|
74
|
-
paddingHorizontal: cx(12.5),
|
|
75
|
-
backgroundColor: props.theme?.tag.background,
|
|
76
|
-
borderRadius: cx(8),
|
|
77
|
-
},
|
|
78
|
-
moodTypeLabelText: {
|
|
79
|
-
height: cx(16),
|
|
80
|
-
color: props.theme?.tag.fontColor,
|
|
81
|
-
fontSize: cx(10),
|
|
82
|
-
textAlignVertical: 'center',
|
|
83
|
-
// fontFamily: 'helvetica_neue_lt_std_roman',
|
|
84
|
-
lineHeight: cx(16),
|
|
85
|
-
},
|
|
86
|
-
})
|
|
43
|
+
), [MoodJumpGradientMode, deviceTypeOption]);
|
|
44
|
+
|
|
45
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
87
46
|
|
|
88
47
|
return (
|
|
89
48
|
<Card style={[styles.card, props.style]} onPress={props.onPress}>
|
|
90
|
-
<View>
|
|
91
|
-
<
|
|
92
|
-
<View style={styles.headline}>
|
|
49
|
+
<View style={styles.contentContainer}>
|
|
50
|
+
<View style={styles.row}>
|
|
93
51
|
<Text style={styles.headText}>{mood.name}</Text>
|
|
94
52
|
<TouchableOpacity style={styles.checkbox} onPress={() => props.onSwitch(!props.enable)}>
|
|
95
|
-
<Image source={{ uri: res.ic_check
|
|
53
|
+
<Image source={{ uri: res.ic_check }}
|
|
54
|
+
style={[styles.checkboxImage, { tintColor: props.enable ? theme?.icon.primary : theme?.icon.disable }]}/>
|
|
96
55
|
</TouchableOpacity>
|
|
97
56
|
</View>
|
|
98
|
-
<Spacer />
|
|
99
|
-
<MixMoodColorsLine
|
|
100
|
-
|
|
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)) && (
|
|
101
64
|
<>
|
|
102
|
-
<Spacer height={cx(7)}
|
|
103
|
-
<MixMoodColorsLine
|
|
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
|
+
/>
|
|
104
71
|
</>
|
|
105
72
|
)}
|
|
106
|
-
<Spacer height={cx(12)}
|
|
107
|
-
<View style={styles.
|
|
73
|
+
<Spacer height={cx(12)}/>
|
|
74
|
+
<View style={styles.row}>
|
|
108
75
|
<View style={styles.moodTypeLabel}>
|
|
109
76
|
<Text style={styles.moodTypeLabelText}>
|
|
110
|
-
{I18n.getLang(
|
|
111
|
-
isDynamic ? 'mood_overview_field_chip_2' : 'mood_overview_field_chip_text'
|
|
112
|
-
)}
|
|
77
|
+
{I18n.getLang(isDynamic ? 'mood_overview_field_chip_2' : 'mood_overview_field_chip_text')}
|
|
113
78
|
</Text>
|
|
114
79
|
</View>
|
|
115
80
|
</View>
|
|
116
|
-
<Spacer height={cx(16)} />
|
|
117
81
|
</View>
|
|
118
82
|
</Card>
|
|
119
83
|
);
|
|
120
84
|
};
|
|
121
85
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}) : ['#eee'];
|
|
132
|
-
|
|
133
|
-
const styles = StyleSheet.create({
|
|
134
|
-
gradientItem: {
|
|
135
|
-
flexDirection: 'row',
|
|
136
|
-
},
|
|
137
|
-
gradientItemIconView: {
|
|
138
|
-
width: cx(24),
|
|
139
|
-
height: cx(24),
|
|
140
|
-
justifyContent: 'center',
|
|
141
|
-
alignItems: 'center',
|
|
142
|
-
backgroundColor: '#aaa',
|
|
143
|
-
borderRadius: cx(8),
|
|
144
|
-
},
|
|
145
|
-
gradientItemIcon: {
|
|
146
|
-
width: cx(16),
|
|
147
|
-
height: cx(16),
|
|
148
|
-
tintColor: '#fff',
|
|
149
|
-
},
|
|
150
|
-
})
|
|
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);
|
|
151
95
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
96
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
97
|
+
const { width } = event.nativeEvent.layout;
|
|
98
|
+
if (width > 0 && width !== measuredWidth) {
|
|
99
|
+
setMeasuredWidth(width);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
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 && (
|
|
155
118
|
<MoodColorsLine
|
|
156
119
|
nodeStyle={{ borderColor: '#ccc', borderWidth: 1 }}
|
|
157
|
-
width={
|
|
158
|
-
type={
|
|
120
|
+
width={finalWidth}
|
|
121
|
+
type={type}
|
|
159
122
|
colors={lightColors}
|
|
160
123
|
/>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
|
|
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
|
+
);
|
|
138
|
+
|
|
139
|
+
// Case 1: 父组件已经计算并传入了宽度 (最高效)
|
|
140
|
+
if (propWidth) {
|
|
141
|
+
return (
|
|
142
|
+
<View style={styles.mixLineRow}>
|
|
143
|
+
{renderColorLine()}
|
|
144
|
+
{renderIcon()}
|
|
145
|
+
</View>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Case 2: 自我测量模式 (回退方案)
|
|
150
|
+
return (
|
|
151
|
+
<View style={styles.mixLineRow}>
|
|
152
|
+
{/* 这个 View (测量器) 会自动收缩以填充'颜色条'应占的空间 */}
|
|
153
|
+
<View style={{ flex: 1 }} onLayout={handleLayout}>
|
|
154
|
+
{renderColorLine()}
|
|
155
|
+
</View>
|
|
156
|
+
{/* 图标作为测量器的兄弟节点,Flexbox 会先为它分配空间 */}
|
|
157
|
+
{renderIcon()}
|
|
169
158
|
</View>
|
|
170
159
|
);
|
|
171
160
|
}
|
|
161
|
+
|
|
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);
|
|
@@ -1,85 +1,117 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
2
3
|
import { Utils } from 'tuya-panel-kit';
|
|
3
4
|
import Card from '@ledvance/base/src/components/Card';
|
|
4
5
|
import { CellContent } from '@ledvance/base/src/components/Cell';
|
|
5
|
-
import { StyleSheet, View } from 'react-native';
|
|
6
6
|
import Spacer from '@ledvance/base/src/components/Spacer';
|
|
7
|
+
import ThemeType from '@ledvance/base/src/config/themeType';
|
|
8
|
+
|
|
7
9
|
import { MoodJumpGradientMode, MoodUIInfo } from './Interface';
|
|
8
|
-
import { MixMoodColorsLine } from './MoodItem';
|
|
9
|
-
import ThemeType from '@ledvance/base/src/config/themeType'
|
|
10
|
+
import { MixMoodColorsLine } from './MoodItem'; // 从 MoodItem.tsx 导入重构后的组件
|
|
10
11
|
|
|
11
12
|
const cx = Utils.RatioUtils.convertX;
|
|
12
|
-
const { withTheme } = Utils.ThemeUtils
|
|
13
|
+
const { withTheme } = Utils.ThemeUtils;
|
|
14
|
+
|
|
15
|
+
// 定义布局中使用的常量,便于维护和计算
|
|
16
|
+
const CARD_MARGIN_HORIZONTAL = cx(24);
|
|
17
|
+
const CONTENT_PADDING_HORIZONTAL = cx(16);
|
|
18
|
+
const ICON_WIDTH = cx(24);
|
|
19
|
+
const ICON_SPACING = cx(7);
|
|
13
20
|
|
|
14
21
|
interface LightCategory {
|
|
15
|
-
isMixLight?: boolean
|
|
16
|
-
isStripLight?: boolean
|
|
17
|
-
isStringLight?: boolean
|
|
18
|
-
isCeilingLight?: boolean
|
|
22
|
+
isMixLight?: boolean;
|
|
23
|
+
isStripLight?: boolean;
|
|
24
|
+
isStringLight?: boolean;
|
|
25
|
+
isCeilingLight?: boolean;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
interface
|
|
22
|
-
theme?: ThemeType
|
|
28
|
+
interface RecommendMoodItemProps {
|
|
29
|
+
theme?: ThemeType;
|
|
23
30
|
title: string;
|
|
24
31
|
isMix: boolean;
|
|
25
32
|
mood: MoodUIInfo;
|
|
26
|
-
deviceTypeOption?: LightCategory
|
|
33
|
+
deviceTypeOption?: LightCategory;
|
|
27
34
|
onPress: () => void;
|
|
35
|
+
containerWidth: number; // 1. 接收从父组件传来的容器宽度
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
const
|
|
31
|
-
const { mood, isMix, deviceTypeOption } = props;
|
|
38
|
+
const RecommendMoodItem = (props: RecommendMoodItemProps) => {
|
|
39
|
+
const { mood, isMix, deviceTypeOption, theme, containerWidth } = props;
|
|
32
40
|
|
|
33
41
|
const gradientMode = useMemo(() => (
|
|
34
42
|
deviceTypeOption?.isStringLight ? MoodJumpGradientMode.StringGradient : deviceTypeOption?.isStripLight ? MoodJumpGradientMode.StripGradient : MoodJumpGradientMode.SourceGradient
|
|
35
|
-
), [MoodJumpGradientMode, deviceTypeOption])
|
|
43
|
+
), [MoodJumpGradientMode, deviceTypeOption]);
|
|
36
44
|
|
|
45
|
+
// 2. 使用 useMemo 根据传入的 prop 精确计算颜色条的宽度
|
|
46
|
+
const lineWidth = useMemo(() => {
|
|
47
|
+
if (containerWidth <= 0) return 0;
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
marginHorizontal: cx(16),
|
|
47
|
-
width: cx(295)
|
|
48
|
-
},
|
|
49
|
-
title: {
|
|
50
|
-
color: props.theme?.global.fontColor,
|
|
51
|
-
fontSize: cx(16),
|
|
52
|
-
// fontFamily: 'helvetica_neue_lt_std_bd',
|
|
53
|
-
},
|
|
54
|
-
lineStyle: {
|
|
55
|
-
alignItems: 'center',
|
|
56
|
-
},
|
|
57
|
-
})
|
|
49
|
+
// 基础宽度 = 容器总宽 - 卡片外边距*2 - 内容内边距*2
|
|
50
|
+
const baseWidth = containerWidth - (CARD_MARGIN_HORIZONTAL * 2) - (CONTENT_PADDING_HORIZONTAL * 2);
|
|
51
|
+
|
|
52
|
+
// 如果是 isMix 模式,还需减去右侧图标的宽度和间距
|
|
53
|
+
return isMix ? baseWidth - ICON_WIDTH - ICON_SPACING : baseWidth;
|
|
54
|
+
}, [containerWidth, isMix]);
|
|
55
|
+
|
|
56
|
+
const styles = useMemo(() => getRecommendStyles(theme), [theme]);
|
|
58
57
|
|
|
59
58
|
return (
|
|
60
|
-
<Card style={styles.
|
|
59
|
+
<Card style={styles.card} onPress={props.onPress}>
|
|
61
60
|
<CellContent
|
|
62
61
|
title={props.title}
|
|
63
62
|
value={''}
|
|
64
63
|
style={styles.content}
|
|
65
64
|
titleStyle={styles.title}
|
|
66
|
-
arrowStyle={{
|
|
67
|
-
color: props.theme?.global.fontColor,
|
|
68
|
-
size: cx(16),
|
|
69
|
-
}}
|
|
65
|
+
arrowStyle={{ color: theme?.global.fontColor, size: cx(16) }}
|
|
70
66
|
/>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
67
|
+
|
|
68
|
+
{/* 3. 只有在计算出有效宽度后才渲染颜色条 */}
|
|
69
|
+
{lineWidth > 0 && mood.mainLamp && (
|
|
70
|
+
<View style={styles.lineContainer}>
|
|
71
|
+
{/* 主灯颜色条 */}
|
|
72
|
+
<MixMoodColorsLine
|
|
73
|
+
width={lineWidth} // 4. 将计算好的宽度传递下去
|
|
74
|
+
isMix={isMix}
|
|
75
|
+
mixSubLight={mood.mainLamp}
|
|
76
|
+
type={mood.mainLamp.mode === gradientMode ? 'gradient' : 'separate'}
|
|
77
|
+
/>
|
|
78
|
+
{/* 副灯颜色条 (如果需要) */}
|
|
79
|
+
{isMix && mood.secondaryLamp && mood.secondaryLamp.nodes.length > 0 && (
|
|
80
|
+
<>
|
|
81
|
+
<Spacer height={cx(7)} />
|
|
82
|
+
<MixMoodColorsLine
|
|
83
|
+
width={lineWidth} // 同样传递计算好的宽度
|
|
84
|
+
isMix={isMix}
|
|
85
|
+
mixSubLight={mood.secondaryLamp}
|
|
86
|
+
type={mood.secondaryLamp.mode === (deviceTypeOption?.isCeilingLight ? MoodJumpGradientMode.StripGradient : MoodJumpGradientMode.SourceGradient) ? 'gradient' : 'separate'}
|
|
87
|
+
/>
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
<Spacer height={cx(12)} />
|
|
91
|
+
</View>
|
|
80
92
|
)}
|
|
81
93
|
</Card>
|
|
82
94
|
);
|
|
83
95
|
};
|
|
84
96
|
|
|
85
|
-
|
|
97
|
+
const getRecommendStyles = (theme?: ThemeType) => StyleSheet.create({
|
|
98
|
+
card: {
|
|
99
|
+
marginHorizontal: CARD_MARGIN_HORIZONTAL,
|
|
100
|
+
paddingVertical: 0,
|
|
101
|
+
paddingHorizontal: 0,
|
|
102
|
+
},
|
|
103
|
+
content: {
|
|
104
|
+
paddingHorizontal: CONTENT_PADDING_HORIZONTAL,
|
|
105
|
+
height: cx(56),
|
|
106
|
+
// 移除了硬编码的 width,使其自适应
|
|
107
|
+
},
|
|
108
|
+
title: {
|
|
109
|
+
color: theme?.global.fontColor,
|
|
110
|
+
fontSize: cx(16),
|
|
111
|
+
},
|
|
112
|
+
lineContainer: {
|
|
113
|
+
paddingHorizontal: CONTENT_PADDING_HORIZONTAL,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export default withTheme(RecommendMoodItem);
|
|
@@ -44,6 +44,7 @@ const OverchargeSwitchPage = (props: { theme?: ThemeType }) => {
|
|
|
44
44
|
|
|
45
45
|
return (<Page
|
|
46
46
|
backText={devInfo.name}
|
|
47
|
+
accessibilityHint={I18n.getLang('switch_overcharge_headline_text')}
|
|
47
48
|
loading={state.loading}>
|
|
48
49
|
<Spacer height={cx(24)}/>
|
|
49
50
|
<Card style={{marginHorizontal: cx(24)}}>
|