@ledvance/ui-biz-bundle 1.0.2 → 1.0.4

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.
Files changed (34) hide show
  1. package/.babelrc +31 -31
  2. package/.eslintignore +5 -5
  3. package/.eslintrc.js +27 -27
  4. package/.prettierrc.js +1 -1
  5. package/.versionrc +5 -5
  6. package/package.json +72 -72
  7. package/rn-cli.config.js +8 -8
  8. package/src/modules/history/HistoryPage.d.ts +2 -2
  9. package/src/modules/history/HistoryPage.tsx +254 -254
  10. package/src/modules/history/SwitchHistoryPageActions.d.ts +13 -13
  11. package/src/modules/history/SwitchHistoryPageActions.ts +53 -53
  12. package/src/modules/hooks/DeviceDpStateHooks.ts +27 -0
  13. package/src/modules/mood/MixLightActions.ts +82 -0
  14. package/src/modules/mood/MixLightSceneActions.ts +259 -0
  15. package/src/modules/mood/MixMoodItem.tsx +138 -0
  16. package/src/modules/mood/MixScene.tsx +131 -0
  17. package/src/modules/mood/MixSceneBeans.ts +62 -0
  18. package/src/modules/mood/MoodAction.ts +222 -0
  19. package/src/modules/mood/MoodItem.tsx +113 -0
  20. package/src/modules/mood/SceneInfo.ts +319 -0
  21. package/src/modules/timeSchedule/DeviceState.tsx +54 -0
  22. package/src/modules/timeSchedule/LdvScheduleItem.tsx +125 -0
  23. package/src/modules/timeSchedule/ManualSetting.tsx +69 -0
  24. package/src/modules/timeSchedule/MoodSetting.tsx +66 -0
  25. package/src/modules/timeSchedule/ScheduleScene.tsx +138 -0
  26. package/src/modules/timeSchedule/SingleLightView.tsx +254 -0
  27. package/src/modules/timeSchedule/TimeScheduleEditpage.tsx +480 -0
  28. package/src/modules/timeSchedule/TimeSchedulePage.tsx +323 -0
  29. package/src/modules/timeSchedule/mix/MixLightBean.ts +10 -0
  30. package/src/modules/timeSchedule/mix/MixLightView.tsx +221 -0
  31. package/src/modules/timeSchedule/utils.ts +7 -0
  32. package/src/modules/timer/TimerPage.tsx +409 -406
  33. package/src/modules/timer/{timerPageAction.ts → TimerPageAction.ts} +91 -91
  34. package/tsconfig.json +50 -50
@@ -0,0 +1,323 @@
1
+ import React, { useCallback, useEffect } from 'react'
2
+ import {RouteProp, useRoute} from '@react-navigation/core'
3
+ import Page from '@ledvance/base/src/components/Page'
4
+ import Tag from '@ledvance/base/src/components/Tag'
5
+ import { useDeviceInfo, useTimeSchedule } from '@ledvance/base/src/models/modules/NativePropsSlice'
6
+ import I18n from '@ledvance/base/src/i18n'
7
+ import res from '@ledvance/base/src/res'
8
+ import { View, Text, FlatList, StyleSheet, ScrollView, Image, TouchableOpacity } from 'react-native'
9
+ import { Utils } from 'tuya-panel-kit'
10
+ import Spacer from '@ledvance/base/src/components/Spacer'
11
+ import { useNavigation } from '@react-navigation/native'
12
+ import { NativeApi } from '@ledvance/base/src/api/native'
13
+ import LdvScheduleItem from './LdvScheduleItem'
14
+ import { showDialog } from '@ledvance/base/src/utils/common'
15
+ import { useReactive } from 'ahooks'
16
+ import { cloneDeep } from 'lodash'
17
+
18
+ const { convertX: cx } = Utils.RatioUtils
19
+
20
+ export type dpItem = {
21
+ label: string
22
+ value: string
23
+ dpId: string
24
+ }
25
+
26
+ type TimeSchedulePageRouteParams = {
27
+ params: { dps: dpItem[], dpCodes: Record<string,string>}
28
+ }
29
+
30
+ type Props = {
31
+ route: RouteProp<TimeSchedulePageRouteParams, 'params'>
32
+ }
33
+
34
+ const TimeSchedulePage = () => {
35
+ const deviceInfo = useDeviceInfo()
36
+ const navigation = useNavigation()
37
+ const { dps, dpCodes } = useRoute<Props['route']>().params
38
+ const state = useReactive({
39
+ timeScheduleList: [] as any[],
40
+ filterScheduleList: [] as any[],
41
+ filterTags: dps ? dps.reduce((pre, cur) => {
42
+ if(cur.dpId){
43
+ pre[cur.dpId] = false
44
+ }
45
+ return pre
46
+ }, {}) : {}
47
+ })
48
+ const [, setTimeSchedule] = useTimeSchedule()
49
+
50
+
51
+ useEffect(() => {
52
+ getTimerScheduleList()
53
+ }, [])
54
+
55
+ useEffect(() => {
56
+ if (Object.values(state.filterTags).every(item => item) || Object.values(state.filterTags).every(item => !item)) {
57
+ state.filterScheduleList = cloneDeep(state.timeScheduleList)
58
+ } else {
59
+ const checkedTags = Object.keys(state.filterTags).filter(tag => state.filterTags[tag])
60
+ const newList = state.timeScheduleList.filter(item => checkedTags.every(tag => item.dps[tag] !== undefined))
61
+ state.filterScheduleList = newList
62
+ }
63
+ }, [state.filterTags])
64
+
65
+ const navigateToEdit = useCallback((item = undefined) => {
66
+ const path = dpCodes.time_schedule_edit
67
+ navigation.navigate(path, {
68
+ name: path,
69
+ scheduleItem: item,
70
+ dpCodes,
71
+ dps,
72
+ reloadData: getTimerScheduleList,
73
+ deleteDialog: (item:any) =>{
74
+ return deleteDialog(item)
75
+ }
76
+ })
77
+ }, [state.timeScheduleList])
78
+
79
+ const getTimerScheduleList = async () => {
80
+ const res: any = await NativeApi.timerList(deviceInfo.devId)
81
+ if (res.result && res.value) {
82
+ // 原生传过来的dps是json字符串。
83
+ const originList = res.value.map(item => {
84
+ const itemDps = JSON.parse(item.dps)
85
+
86
+ const dps = {}
87
+ Object.keys(itemDps).forEach((dpCode: string) => {
88
+ if (Object.values(dpCodes).findIndex(item => item === dpCode) !== -1) {
89
+ dps[dpCode] = itemDps[dpCode]
90
+ }
91
+ })
92
+ return {
93
+ ...item,
94
+ dps: dps,
95
+ }
96
+ })
97
+ state.timeScheduleList = originList
98
+ state.filterScheduleList = originList
99
+ setTimeSchedule(Symbol())
100
+ }
101
+ }
102
+
103
+ const itemOnValueChange = item => {
104
+
105
+ const value = {
106
+ ...item,
107
+ status: !item.status,
108
+ }
109
+
110
+ NativeApi.editTimer(deviceInfo.devId, value, res => {
111
+ if (res.result) {
112
+ const newList = state.timeScheduleList.map(mItem => {
113
+ return {
114
+ ...mItem,
115
+ status: mItem.id === item.id ? value.status : mItem.status,
116
+ }
117
+ })
118
+ state.timeScheduleList = newList
119
+ state.filterScheduleList = newList
120
+ setTimeSchedule(Symbol())
121
+ }
122
+ })
123
+ }
124
+
125
+
126
+ const onDelete = (item) => {
127
+ NativeApi.deleteTimer(deviceInfo.devId, item, res => {
128
+ if (res.result) {
129
+ const newList = state.timeScheduleList.filter(nItem => item.id !== nItem.id)
130
+ state.timeScheduleList = newList
131
+ state.filterScheduleList = newList
132
+ setTimeSchedule(Symbol())
133
+ }
134
+ })
135
+ }
136
+
137
+ const changeFilter = useCallback((v:boolean, tag: string) =>{
138
+ state.filterTags = {
139
+ ...state.filterTags,
140
+ [tag]: v
141
+ }
142
+ }, [])
143
+
144
+ // 删除弹框
145
+ const deleteDialog = (item: any) =>{
146
+ return new Promise(resolve =>{
147
+ showDialog({
148
+ method: 'confirm',
149
+ title: I18n.getLang('cancel_dialog_delete_item_timeschedule_titel'),
150
+ subTitle: I18n.getLang('cancel_dialog_delete_item_timeschedule_description'),
151
+ onConfirm: (_, { close }) =>{
152
+ onDelete(item)
153
+ close()
154
+ resolve(true)
155
+ }
156
+ })
157
+ })
158
+ }
159
+
160
+ const renderItem = ({item}) => {
161
+ return (
162
+ <LdvScheduleItem
163
+ item={item}
164
+ tags={dps}
165
+ onEnableChange={itemOnValueChange}
166
+ onPress={navigateToEdit}
167
+ onLongPress={(item) => {
168
+ deleteDialog(item).then()
169
+ }}
170
+ />
171
+ )
172
+ }
173
+
174
+ const showTags = useCallback(() =>{
175
+ return Object.values(state.filterTags).length > 1 && state.timeScheduleList.length > 0
176
+ }, [state.filterTags, state.timeScheduleList])
177
+
178
+ const showScheduleList = useCallback(() =>{
179
+ return !!state.filterScheduleList.length
180
+ }, [state.filterScheduleList])
181
+
182
+ const getLabelByDp = (dp:string) =>{
183
+ const dpLabel = dps.reduce((pre, cur) => {
184
+ if(cur.dpId){
185
+ pre[cur.dpId] = cur.label
186
+ }
187
+ return pre
188
+ }, {})
189
+ return dpLabel[dp]
190
+ }
191
+
192
+ return (
193
+ <Page
194
+ backText={deviceInfo.name}
195
+ onBackClick={navigation.goBack}
196
+ headlineText={I18n.getLang('timeschedule_overview_headline_text')}
197
+ headlineIcon={res.device_panel_schedule_add}
198
+ onHeadlineIconClick={() => {
199
+ navigateToEdit()
200
+ }}
201
+ >
202
+ <View style={styles.content}>
203
+ <ScrollView nestedScrollEnabled={true}>
204
+ <Text style={{ color: '#000', marginLeft: cx(24) }}>{I18n.getLang('timeschedule_overview_description_text')}</Text>
205
+ <Spacer />
206
+ {showTags() && <View style={styles.categoryList}>
207
+ {Object.keys(state.filterTags).map(tag =>{
208
+ return <Tag
209
+ key={tag}
210
+ text={getLabelByDp(tag)}
211
+ checked={state.filterTags[tag]}
212
+ onCheckedChange={(v) => changeFilter(v, tag)}
213
+ style={{ marginRight: cx(5) }}
214
+ />
215
+ })}
216
+ </View>}
217
+ {showScheduleList()
218
+ ?
219
+ <FlatList
220
+ data={state.filterScheduleList}
221
+ keyExtractor={(item: any) => item.id}
222
+ renderItem={renderItem}
223
+ />
224
+ :
225
+ <View style={styles.emptyListCon}>
226
+ <Image
227
+ style={styles.emptyImage}
228
+ source={{ uri: res.ldv_timer_empty }}
229
+ resizeMode="contain"
230
+ />
231
+ <View
232
+ style={{
233
+ marginHorizontal: cx(24),
234
+ flexDirection: 'row',
235
+ justifyContent: 'center',
236
+ alignItems: 'center',
237
+ marginTop: cx(28),
238
+ }}
239
+ >
240
+ <Image
241
+ source={{ uri: res.device_panel_schedule_alert }}
242
+ style={{ width: cx(16), height: cx(16) }}
243
+ />
244
+ <Text
245
+ style={{
246
+ color: '#000',
247
+ fontSize: cx(12),
248
+ }}
249
+ >
250
+ {I18n.getLang(state.timeScheduleList.length ? 'sleepwakeschedule_empty_filtering_information_text' : 'timeschedule_overview_empty_information_text')}
251
+ </Text>
252
+ </View>
253
+ {!showScheduleList()
254
+ &&
255
+ <View
256
+ style={{
257
+ height: cx(36),
258
+ width: cx(150),
259
+ marginVertical: cx(25),
260
+ marginHorizontal: cx(24),
261
+ borderRadius: cx(6),
262
+ backgroundColor: '#f60',
263
+ }}
264
+ >
265
+ <TouchableOpacity
266
+ style={{
267
+ flex: 1,
268
+ justifyContent: 'center',
269
+ alignItems: 'center',
270
+ }}
271
+ onPress={() => navigateToEdit()}
272
+ >
273
+ <Text style={{ fontSize: 12, fontWeight: 'bold', color: '#fff' }}>
274
+ {I18n.getLang('timeschedule_overview_empty_button_add_text')}
275
+ </Text>
276
+ </TouchableOpacity>
277
+ </View>
278
+ }
279
+ </View>
280
+ }
281
+ </ScrollView>
282
+ </View>
283
+ </Page>
284
+ )
285
+ }
286
+
287
+ const styles = StyleSheet.create({
288
+ content: {
289
+ // marginHorizontal: cx(20)
290
+ flex: 1
291
+ },
292
+ categoryList: {
293
+ flexDirection: 'row',
294
+ flexWrap: 'wrap',
295
+ marginHorizontal: cx(24)
296
+ },
297
+ emptyListCon: {
298
+ flex: 1,
299
+ justifyContent: 'center',
300
+ alignItems: 'center',
301
+ marginTop: cx(60),
302
+ },
303
+ emptyImage: {
304
+ width: cx(225),
305
+ height: cx(198),
306
+ },
307
+ emptyNoTime: {
308
+ fontSize: cx(14),
309
+ lineHeight: cx(20),
310
+ marginTop: cx(30),
311
+ width: '76%',
312
+ textAlign: 'center',
313
+ },
314
+ emptyTimeTip: {
315
+ fontSize: cx(12),
316
+ lineHeight: cx(17),
317
+ marginTop: cx(6),
318
+ width: '76%',
319
+ textAlign: 'center',
320
+ },
321
+ })
322
+
323
+ export default TimeSchedulePage
@@ -0,0 +1,10 @@
1
+ export interface MixLightBean {
2
+ whiteLightSwitch: boolean,
3
+ colorLightSwitch: boolean,
4
+ mixRgbcwEnabled: boolean,
5
+ hue: number, // 色相 0-360
6
+ sat: number, // 饱和度 0-100
7
+ lightness: number, // 明度 0-100
8
+ brightness: number, // 亮度 0-100
9
+ colorTempPercent: number, // 色温 0-100
10
+ }
@@ -0,0 +1,221 @@
1
+ import React, { useEffect } from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import ldvStyles from 'config/ldvConfig'
4
+ import LdvSwitch from '@ledvance/base/src/components/ldvSwitch'
5
+ import I18n from '@ledvance/base/src/i18n'
6
+ import LdvColorSlider from '@ledvance/base/src/components/ldvColorSlider'
7
+ import LdvPresetView from '@ledvance/base/src/components/ldvPresetView'
8
+ import { Utils } from 'tuya-panel-kit'
9
+ import { useReactive } from 'ahooks'
10
+ import LdvSlider from '@ledvance/base/src/components/ldvSlider'
11
+ import { hex2Hsv, hsv2Hex } from '@ledvance/base/src/utils'
12
+ import LdvColorBrightness from '@ledvance/base/src/components/ldvColorBrightness'
13
+ import LdvSaturation from '@ledvance/base/src/components/ldvSaturation'
14
+ import { cctToColor } from '@ledvance/base/src/utils/cctUtils'
15
+ import { mapFloatToRange } from '@ledvance/base/src/utils'
16
+ import { Buffer } from 'buffer';
17
+ import { dp2Obj, obj2Dp } from '../../mood/MixLightActions'
18
+
19
+ const { convertX: cx } = Utils.RatioUtils
20
+
21
+ interface MixLightViewProps {
22
+ scheduleItem: any
23
+ dpCodes: Record<string, string>
24
+ setEnable: (enable: boolean, idx: number) => void
25
+ setSendDps: (dp: Record<string, any>) => void
26
+ }
27
+
28
+ export default function MixLightView(props: MixLightViewProps) {
29
+ const { scheduleItem, dpCodes, setEnable, setSendDps } = props
30
+ const state = useReactive({
31
+ actionMix: {
32
+ whiteLightSwitch: true,
33
+ colorLightSwitch: false,
34
+ mixRgbcwEnabled: true,
35
+ hue: 360,
36
+ sat: 100,
37
+ lightness: 100,
38
+ brightness: 100,
39
+ colorTempPercent: 100,
40
+ },
41
+ flag: Symbol(),
42
+ })
43
+
44
+ useEffect(() => {
45
+ if (scheduleItem) {
46
+ if (scheduleItem.dps[dpCodes.mix_rgbcw] !== undefined) {
47
+ const base64String = Buffer.from(scheduleItem.dps[dpCodes.mix_rgbcw], 'base64').toString('hex')
48
+ state.actionMix = dp2Obj(base64String)
49
+ }
50
+ }
51
+ }, [])
52
+
53
+ useEffect(() =>{
54
+ const dp = getSendDps()
55
+ setSendDps(dp)
56
+ }, [state.flag])
57
+
58
+ const getSendDps = () =>{
59
+ const dpsString = obj2Dp(state.actionMix);
60
+ return {
61
+ [props.dpCodes.switch_led]: true,
62
+ [props.dpCodes.mix_rgbcw]: Buffer.from(dpsString, 'hex').toString('base64'),
63
+ };
64
+ }
65
+
66
+ return (
67
+ <View>
68
+ <View style={styles.container}>
69
+ <View style={[styles.contentBG, ldvStyles.shadow]}>
70
+ <LdvSwitch
71
+ title={I18n.getLang('light_sources_tile_main_lighting_headline')}
72
+ color={cctToColor(state.actionMix.colorTempPercent.toFixed())}
73
+ colorAlpha={1}
74
+ enable={state.actionMix.whiteLightSwitch}
75
+ setEnable={async value => {
76
+ state.actionMix.whiteLightSwitch = value
77
+ }} />
78
+ {state.actionMix.whiteLightSwitch && (<View>
79
+ <View style={[styles.shadeBg]}>
80
+ <LdvColorSlider
81
+ type={'temperature'}
82
+ title={I18n.getLang('light_sources_tile_main_lighting_shade')}
83
+ thumbColor={cctToColor(state.actionMix.colorTempPercent.toFixed())}
84
+ value={state.actionMix.colorTempPercent}
85
+ onValueChange={value => {
86
+ state.actionMix.colorTempPercent = value
87
+
88
+ }}
89
+ onSlidingComplete={value => {
90
+ state.actionMix.colorTempPercent = value
91
+ state.flag = Symbol()
92
+ }} />
93
+ <LdvPresetView
94
+ type={'temperature'}
95
+ style={styles.presetView}
96
+ onPress={item => {
97
+ state.actionMix.colorTempPercent = item.value
98
+ // state.brightness = 100
99
+ state.flag = Symbol()
100
+ }} />
101
+ </View>
102
+ <LdvSlider
103
+ title={I18n.getLang('light_sources_tile_rgb_lighting_brightness')}
104
+ value={Number(state.actionMix.brightness.toFixed())}
105
+ onValueChange={value => {
106
+ state.actionMix.brightness = value > 100 ? 100 : value
107
+ }}
108
+ onSlidingComplete={value => {
109
+ state.actionMix.brightness = value > 100 ? 100 : value
110
+ state.flag = Symbol()
111
+ }} />
112
+ <View style={{ height: cx(20) }} />
113
+ </View>)}
114
+ </View>
115
+ </View>
116
+
117
+ <View style={[styles.container]}>
118
+ <View style={[styles.contentBG, ldvStyles.shadow]}>
119
+ <LdvSwitch
120
+ title={I18n.getLang('light_sources_tile_sec_lighting_headline')}
121
+ color={hsv2Hex(state.actionMix.hue, Math.round(mapFloatToRange(state.actionMix.sat / 100, 30, 100)), 100)}
122
+ colorAlpha={1}
123
+ enable={state.actionMix.colorLightSwitch}
124
+ setEnable={value => {
125
+ state.actionMix.colorLightSwitch = value
126
+ }} />
127
+
128
+ {state.actionMix.colorLightSwitch && (
129
+ <View>
130
+ <View style={[styles.shadeBg]}>
131
+ <LdvColorSlider
132
+ type={'color'}
133
+ title={I18n.getLang('light_sources_tile_sec_lighting_shade')}
134
+ thumbColor={hsv2Hex(state.actionMix.hue, 100, 100)}
135
+ value={state.actionMix.hue}
136
+ onValueChange={value => {
137
+ state.actionMix.hue = value
138
+
139
+ }}
140
+ onSlidingComplete={(value) => {
141
+ state.actionMix.hue = value
142
+ state.flag = Symbol()
143
+ }} />
144
+ <LdvPresetView
145
+ type={'color'}
146
+ style={styles.presetView}
147
+ onPress={(item) => {
148
+ const hsv = hex2Hsv(item.color)
149
+ if (hsv) {
150
+ state.actionMix.hue = hsv.h
151
+ }
152
+ state.flag = Symbol()
153
+ }} />
154
+ </View>
155
+ <View>
156
+ <LdvSaturation
157
+ value={state.actionMix.sat}
158
+ onValueChange={value => {
159
+ state.actionMix.sat = value
160
+
161
+ }}
162
+ onSlidingComplete={(value) => {
163
+ state.actionMix.sat = value
164
+ state.flag = Symbol()
165
+ }}
166
+
167
+ />
168
+ </View>
169
+ <View style={{ height: cx(14) }} />
170
+ <LdvColorBrightness
171
+ value={state.actionMix.lightness}
172
+ onValueChange={value => {
173
+ state.actionMix.lightness = value
174
+
175
+ }}
176
+ onSlidingComplete={(value) => {
177
+ state.actionMix.lightness = value
178
+ state.flag = Symbol()
179
+ }} />
180
+ <View style={{ height: cx(20) }} />
181
+ </View>
182
+ )}
183
+ </View>
184
+ </View>
185
+ </View>
186
+ )
187
+ }
188
+
189
+ const styles = StyleSheet.create({
190
+ container: {
191
+ flexDirection: 'column',
192
+ },
193
+ contentBG: {
194
+ flex: 1,
195
+ marginTop: cx(12),
196
+ marginBottom: cx(12),
197
+ marginLeft: cx(24),
198
+ marginRight: cx(24),
199
+ },
200
+ titleBGView: {
201
+ flexDirection: 'row',
202
+ alignItems: 'center',
203
+ justifyContent: 'space-between',
204
+ },
205
+ title: {
206
+ fontSize: cx(16),
207
+ fontWeight: 'bold',
208
+ marginVertical: cx(16),
209
+ marginLeft: cx(16),
210
+ },
211
+ switch: {
212
+ marginRight: cx(16),
213
+ },
214
+ shadeBg: {
215
+ height: cx(117),
216
+ flexDirection: 'column',
217
+ },
218
+ presetView: {
219
+ height: cx(60),
220
+ },
221
+ })
@@ -0,0 +1,7 @@
1
+ export function mapFloatToRange(value: number, min: number, max: number): number {
2
+ // 确保 value 在 [0, 1] 范围内
3
+ value = Math.max(0, Math.min(1, value))
4
+
5
+ // 计算插值后的值
6
+ return min + value * (max - min)
7
+ }