@ledvance/ui-biz-bundle 1.0.2 → 1.0.3

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 (35) 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 +325 -0
  29. package/src/modules/timeSchedule/TimeSchedulePageEdit.tsx +0 -0
  30. package/src/modules/timeSchedule/mix/MixLightBean.ts +10 -0
  31. package/src/modules/timeSchedule/mix/MixLightView.tsx +221 -0
  32. package/src/modules/timeSchedule/utils.ts +7 -0
  33. package/src/modules/timer/TimerPage.tsx +409 -406
  34. package/src/modules/timer/{timerPageAction.ts → TimerPageAction.ts} +91 -91
  35. package/tsconfig.json +50 -50
@@ -0,0 +1,325 @@
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 '@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 { RouterKey } from 'navigation/Router'
13
+ import { NativeApi } from '@ledvance/base/src/api/native'
14
+ import LdvScheduleItem from './LdvScheduleItem'
15
+ import dpCodes from "config/dpCodes";
16
+ import { showDialog } from '@ledvance/base/src/utils/common'
17
+ import { useReactive } from 'ahooks'
18
+ import { cloneDeep } from 'lodash'
19
+
20
+ const { convertX: cx } = Utils.RatioUtils
21
+
22
+ export type dpItem = {
23
+ label: string
24
+ value: string
25
+ dpId: string
26
+ }
27
+
28
+ type TimeSchedulePageRouteParams = {
29
+ params: { dps: dpItem[]}
30
+ }
31
+
32
+ type Props = {
33
+ route: RouteProp<TimeSchedulePageRouteParams, 'params'>
34
+ }
35
+
36
+ const TimeSchedulePage = () => {
37
+ const deviceInfo = useDeviceInfo()
38
+ const navigation = useNavigation()
39
+ const { dps } = useRoute<Props['route']>().params
40
+ const state = useReactive({
41
+ timeScheduleList: [] as any[],
42
+ filterScheduleList: [] as any[],
43
+ filterTags: dps ? dps.reduce((pre, cur) => {
44
+ if(cur.dpId){
45
+ pre[cur.dpId] = false
46
+ }
47
+ return pre
48
+ }, {}) : {}
49
+ })
50
+ const [, setTimeSchedule] = useTimeSchedule()
51
+
52
+
53
+ useEffect(() => {
54
+ getTimerScheduleList()
55
+ }, [])
56
+
57
+ useEffect(() => {
58
+ if (Object.values(state.filterTags).every(item => item) || Object.values(state.filterTags).every(item => !item)) {
59
+ state.filterScheduleList = cloneDeep(state.timeScheduleList)
60
+ } else {
61
+ const checkedTags = Object.keys(state.filterTags).filter(tag => state.filterTags[tag])
62
+ const newList = state.timeScheduleList.filter(item => checkedTags.every(tag => item.dps[tag] !== undefined))
63
+ state.filterScheduleList = newList
64
+ }
65
+ }, [state.filterTags])
66
+
67
+ const navigateToEdit = useCallback((item = undefined) => {
68
+ const path = RouterKey.time_schedule_edit
69
+ navigation.navigate(path, {
70
+ name: path,
71
+ scheduleItem: item,
72
+ dpCodes,
73
+ dps,
74
+ reloadData: getTimerScheduleList,
75
+ deleteDialog: (item:any) =>{
76
+ return deleteDialog(item)
77
+ }
78
+ })
79
+ }, [state.timeScheduleList])
80
+
81
+ const getTimerScheduleList = async () => {
82
+ const res: any = await NativeApi.timerList(deviceInfo.devId)
83
+ if (res.result && res.value) {
84
+ // 原生传过来的dps是json字符串。
85
+ const originList = res.value.map(item => {
86
+ const itemDps = JSON.parse(item.dps)
87
+
88
+ const dps = {}
89
+ Object.keys(itemDps).forEach((dpCode: string) => {
90
+ if (Object.values(dpCodes).findIndex(item => item === dpCode) !== -1) {
91
+ dps[dpCode] = itemDps[dpCode]
92
+ }
93
+ })
94
+ return {
95
+ ...item,
96
+ dps: dps,
97
+ }
98
+ })
99
+ state.timeScheduleList = originList
100
+ state.filterScheduleList = originList
101
+ setTimeSchedule(Symbol())
102
+ }
103
+ }
104
+
105
+ const itemOnValueChange = item => {
106
+
107
+ const value = {
108
+ ...item,
109
+ status: !item.status,
110
+ }
111
+
112
+ NativeApi.editTimer(deviceInfo.devId, value, res => {
113
+ if (res.result) {
114
+ const newList = state.timeScheduleList.map(mItem => {
115
+ return {
116
+ ...mItem,
117
+ status: mItem.id === item.id ? value.status : mItem.status,
118
+ }
119
+ })
120
+ state.timeScheduleList = newList
121
+ state.filterScheduleList = newList
122
+ setTimeSchedule(Symbol())
123
+ }
124
+ })
125
+ }
126
+
127
+
128
+ const onDelete = (item) => {
129
+ NativeApi.deleteTimer(deviceInfo.devId, item, res => {
130
+ if (res.result) {
131
+ const newList = state.timeScheduleList.filter(nItem => item.id !== nItem.id)
132
+ state.timeScheduleList = newList
133
+ state.filterScheduleList = newList
134
+ setTimeSchedule(Symbol())
135
+ }
136
+ })
137
+ }
138
+
139
+ const changeFilter = useCallback((v:boolean, tag: string) =>{
140
+ state.filterTags = {
141
+ ...state.filterTags,
142
+ [tag]: v
143
+ }
144
+ }, [])
145
+
146
+ // 删除弹框
147
+ const deleteDialog = (item: any) =>{
148
+ return new Promise(resolve =>{
149
+ showDialog({
150
+ method: 'confirm',
151
+ title: I18n.getLang('cancel_dialog_delete_item_timeschedule_titel'),
152
+ subTitle: I18n.getLang('cancel_dialog_delete_item_timeschedule_description'),
153
+ onConfirm: (_, { close }) =>{
154
+ onDelete(item)
155
+ close()
156
+ resolve(true)
157
+ }
158
+ })
159
+ })
160
+ }
161
+
162
+ const renderItem = ({item}) => {
163
+ return (
164
+ <LdvScheduleItem
165
+ item={item}
166
+ tags={dps}
167
+ onEnableChange={itemOnValueChange}
168
+ onPress={navigateToEdit}
169
+ onLongPress={(item) => {
170
+ deleteDialog(item).then()
171
+ }}
172
+ />
173
+ )
174
+ }
175
+
176
+ const showTags = useCallback(() =>{
177
+ return Object.values(state.filterTags).length > 1 && state.timeScheduleList.length > 0
178
+ }, [state.filterTags, state.timeScheduleList])
179
+
180
+ const showScheduleList = useCallback(() =>{
181
+ return !!state.filterScheduleList.length
182
+ }, [state.filterScheduleList])
183
+
184
+ const getLabelByDp = (dp:string) =>{
185
+ const dpLabel = dps.reduce((pre, cur) => {
186
+ if(cur.dpId){
187
+ pre[cur.dpId] = cur.label
188
+ }
189
+ return pre
190
+ }, {})
191
+ return dpLabel[dp]
192
+ }
193
+
194
+ return (
195
+ <Page
196
+ backText={deviceInfo.name}
197
+ onBackClick={navigation.goBack}
198
+ headlineText={I18n.getLang('timeschedule_overview_headline_text')}
199
+ headlineIcon={res.device_panel_schedule_add}
200
+ onHeadlineIconClick={() => {
201
+ navigateToEdit()
202
+ }}
203
+ >
204
+ <View style={styles.content}>
205
+ <ScrollView nestedScrollEnabled={true}>
206
+ <Text style={{ color: '#000', marginLeft: cx(24) }}>{I18n.getLang('timeschedule_overview_description_text')}</Text>
207
+ <Spacer />
208
+ {showTags() && <View style={styles.categoryList}>
209
+ {Object.keys(state.filterTags).map(tag =>{
210
+ return <Tag
211
+ key={tag}
212
+ text={getLabelByDp(tag)}
213
+ checked={state.filterTags[tag]}
214
+ onCheckedChange={(v) => changeFilter(v, tag)}
215
+ style={{ marginRight: cx(5) }}
216
+ />
217
+ })}
218
+ </View>}
219
+ {showScheduleList()
220
+ ?
221
+ <FlatList
222
+ data={state.filterScheduleList}
223
+ keyExtractor={(item: any) => item.id}
224
+ renderItem={renderItem}
225
+ />
226
+ :
227
+ <View style={styles.emptyListCon}>
228
+ <Image
229
+ style={styles.emptyImage}
230
+ source={{ uri: res.ldv_timer_empty }}
231
+ resizeMode="contain"
232
+ />
233
+ <View
234
+ style={{
235
+ marginHorizontal: cx(24),
236
+ flexDirection: 'row',
237
+ justifyContent: 'center',
238
+ alignItems: 'center',
239
+ marginTop: cx(28),
240
+ }}
241
+ >
242
+ <Image
243
+ source={{ uri: res.device_panel_schedule_alert }}
244
+ style={{ width: cx(16), height: cx(16) }}
245
+ />
246
+ <Text
247
+ style={{
248
+ color: '#000',
249
+ fontSize: cx(12),
250
+ }}
251
+ >
252
+ {I18n.getLang(state.timeScheduleList.length ? 'sleepwakeschedule_empty_filtering_information_text' : 'timeschedule_overview_empty_information_text')}
253
+ </Text>
254
+ </View>
255
+ {!showScheduleList()
256
+ &&
257
+ <View
258
+ style={{
259
+ height: cx(36),
260
+ width: cx(150),
261
+ marginVertical: cx(25),
262
+ marginHorizontal: cx(24),
263
+ borderRadius: cx(6),
264
+ backgroundColor: '#f60',
265
+ }}
266
+ >
267
+ <TouchableOpacity
268
+ style={{
269
+ flex: 1,
270
+ justifyContent: 'center',
271
+ alignItems: 'center',
272
+ }}
273
+ onPress={() => navigateToEdit()}
274
+ >
275
+ <Text style={{ fontSize: 12, fontWeight: 'bold', color: '#fff' }}>
276
+ {I18n.getLang('timeschedule_overview_empty_button_add_text')}
277
+ </Text>
278
+ </TouchableOpacity>
279
+ </View>
280
+ }
281
+ </View>
282
+ }
283
+ </ScrollView>
284
+ </View>
285
+ </Page>
286
+ )
287
+ }
288
+
289
+ const styles = StyleSheet.create({
290
+ content: {
291
+ // marginHorizontal: cx(20)
292
+ flex: 1
293
+ },
294
+ categoryList: {
295
+ flexDirection: 'row',
296
+ flexWrap: 'wrap',
297
+ marginHorizontal: cx(24)
298
+ },
299
+ emptyListCon: {
300
+ flex: 1,
301
+ justifyContent: 'center',
302
+ alignItems: 'center',
303
+ marginTop: cx(60),
304
+ },
305
+ emptyImage: {
306
+ width: cx(225),
307
+ height: cx(198),
308
+ },
309
+ emptyNoTime: {
310
+ fontSize: cx(14),
311
+ lineHeight: cx(20),
312
+ marginTop: cx(30),
313
+ width: '76%',
314
+ textAlign: 'center',
315
+ },
316
+ emptyTimeTip: {
317
+ fontSize: cx(12),
318
+ lineHeight: cx(17),
319
+ marginTop: cx(6),
320
+ width: '76%',
321
+ textAlign: 'center',
322
+ },
323
+ })
324
+
325
+ 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
+ }