@ledvance/ui-biz-bundle 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "@ledvance/ui-biz-bundle",
5
5
  "pid": [],
6
6
  "uiid": "",
7
- "version": "1.0.0",
7
+ "version": "1.0.2",
8
8
  "scripts": {},
9
9
  "dependencies": {
10
10
  "@ledvance/base": "^1.x",
@@ -0,0 +1,406 @@
1
+ import React, { useEffect } from "react";
2
+ import { View, Text, ScrollView, TouchableOpacity, StyleSheet, FlatList, Image } from "react-native"
3
+ import { useNavigation } from '@react-navigation/native'
4
+ import { RouteProp, useRoute } from '@react-navigation/core'
5
+ import { useReactive } from "ahooks";
6
+ import res from '@ledvance/base/src/res'
7
+ import Card from '@ledvance/base/src/components/Card'
8
+ import Spacer from '@ledvance/base/src/components/Spacer'
9
+ import I18n from '@ledvance/base/src/i18n'
10
+ import Page from "@ledvance/base/src/components/Page";
11
+ import LdvPickerView from '@ledvance/base/src/components/ldvPickerView'
12
+ import { Utils, Progress } from 'tuya-panel-kit'
13
+ import { useDeviceInfo } from "@ledvance/base/src/models/modules/NativePropsSlice";
14
+ import { useCountdowns, useProgress } from "./timerPageAction";
15
+ import { cloneDeep } from "lodash";
16
+ import dayjs from "dayjs";
17
+
18
+ export type dpItem = {
19
+ label: string
20
+ value: string
21
+ dpId: string
22
+ enableDp: string
23
+ cloudKey: any
24
+ stringOn: any
25
+ stringOff: any
26
+ }
27
+
28
+ type TimerPageRouteParams = {
29
+ params: { dps: dpItem[] }
30
+ }
31
+
32
+ type Props = {
33
+ route: RouteProp<TimerPageRouteParams, 'params'>
34
+ }
35
+
36
+ const { convertX: cx } = Utils.RatioUtils
37
+ const TimerPage = () => {
38
+ const devInfo = useDeviceInfo()
39
+ const navigation = useNavigation()
40
+ const { dps } = useRoute<Props['route']>().params
41
+ const countdowns = useCountdowns(dps)
42
+ const cancelTimer = (dpId: string) => {
43
+ const countdown = countdowns.find(c => c.dpId === dpId)?.countdown
44
+ countdown && countdown[1](0)
45
+ }
46
+ const progress = useProgress(dps, cancelTimer)
47
+
48
+ const state = useReactive({
49
+ hour: '00',
50
+ minute: '01',
51
+ skillList: [] as dpItem[],
52
+ selectedSkill: [] as dpItem[]
53
+ })
54
+
55
+
56
+ const hasNotRunningTimer = () => {
57
+ return !!countdowns.find(timer => timer.countdown[0] === 0)
58
+ }
59
+
60
+ const runningTimer = () => {
61
+ return countdowns.filter(timer => timer.countdown[0] > 0)
62
+ }
63
+
64
+ const getProgressByDp = (dpId: string) => {
65
+ return progress.find(p => p.dpId === dpId)!.progressHook
66
+ }
67
+
68
+ useEffect(() => {
69
+ if(countdowns.length > 1){
70
+ state.skillList = countdowns.filter(c => c.countdown[0] === 0)
71
+ }else{
72
+ state.skillList = []
73
+ state.selectedSkill = cloneDeep(dps)
74
+ }
75
+ }, [runningTimer().length])
76
+
77
+
78
+ const handelTimer = (timer: dpItem, isAdd: boolean) => {
79
+ if (isAdd) {
80
+ state.selectedSkill = [...state.selectedSkill, timer]
81
+ state.skillList = state.skillList.filter(item => item.dpId !== timer.dpId)
82
+ } else {
83
+ state.selectedSkill = state.selectedSkill.filter(item => item.dpId !== timer.dpId)
84
+ state.skillList = [...state.skillList, timer]
85
+ }
86
+ }
87
+
88
+ const onStartPress = () => {
89
+ let mCountdown = 0
90
+ const hour = parseInt(state.hour)
91
+ const minute = parseInt(state.minute)
92
+ if (hour > 0) {
93
+ mCountdown += hour * 60 * 60
94
+ }
95
+ if (minute > 0) {
96
+ mCountdown += minute * 60
97
+ }
98
+ const time = dayjs()
99
+ .add(Number(hour), 'h')
100
+ .add(Number(minute), 'm')
101
+ .format('YYYY-MM-DD HH:mm:ss');
102
+ if (!state.selectedSkill.length || mCountdown === 0) return
103
+
104
+ state.selectedSkill.forEach(skill => {
105
+ const timer = countdowns.find(timer => timer.dpId === skill.dpId)
106
+ timer?.countdown[1](mCountdown)
107
+ getProgressByDp(skill.dpId)[1](mCountdown, time)
108
+ })
109
+ state.selectedSkill = []
110
+ }
111
+
112
+ const getActiveTimeString = (c: number) => {
113
+ const h = parseInt(String(c / 3600))
114
+ const m = Number(((c - 3600 * h) / 60).toFixed(0))
115
+ if (h > 0) {
116
+ if (m === 0) {
117
+ return h + ' h'
118
+ } else {
119
+ return h + ' h ' + m + ' min'
120
+ }
121
+ } else {
122
+ return m + ' min'
123
+ }
124
+ }
125
+
126
+ const getSingleLightEndTimeString = (mCountdown: number) => {
127
+ const date = new Date()
128
+ const now = date.getTime()
129
+ const after = new Date(now + mCountdown * 1000)
130
+ const timeString = `${after.getHours()}:${after.getMinutes().toString().padStart(2, '0')}`
131
+ return timeString
132
+ }
133
+
134
+ const showActiveView = () => {
135
+ return countdowns.length > 1
136
+ }
137
+
138
+ const renderItem = ({ item }) => {
139
+ return (
140
+ <View style={{
141
+ flexDirection: 'row',
142
+ justifyContent: 'space-between',
143
+ alignItems: 'center',
144
+ backgroundColor: '#fff',
145
+ marginBottom: cx(8)
146
+ }}>
147
+ <Text
148
+ style={{
149
+ color: '#000',
150
+ fontSize: 14,
151
+ marginHorizontal: cx(6),
152
+ marginVertical: cx(9),
153
+ }}
154
+ >
155
+ {item.label}
156
+ </Text>
157
+ {countdowns.length > 1 && <TouchableOpacity onPress={() => handelTimer(item, false)}>
158
+ <Image style={{ width: cx(16), height: cx(16), marginRight: cx(5) }} source={res.ic_arrows_nav_clear} />
159
+ </TouchableOpacity>}
160
+ </View>
161
+ );
162
+ };
163
+
164
+ return (
165
+ <Page
166
+ backText={devInfo.name}
167
+ headlineText={I18n.getLang('timer_nightplug_headline_text')}
168
+ onBackClick={navigation.goBack}
169
+ >
170
+ <ScrollView
171
+ nestedScrollEnabled={true}
172
+ showsHorizontalScrollIndicator={false}
173
+ showsVerticalScrollIndicator={false}
174
+ >
175
+ {/* picker */}
176
+ <View style={styles.content}>
177
+ {hasNotRunningTimer() && <><LdvPickerView
178
+ hour={state.hour}
179
+ minute={state.minute}
180
+ setHour={h => state.hour = h}
181
+ setMinute={m => state.minute = m}
182
+ unit={['h', 'min']}
183
+ />
184
+ {/* Apply for */}
185
+ <View>
186
+ <Text style={styles.itemTitle}>{I18n.getLang('timeschedule_add_schedule_subheadline_text')}</Text>
187
+ <View
188
+ style={{
189
+ backgroundColor: '#f6f6f6',
190
+ borderRadius: 4,
191
+ minHeight: cx(50),
192
+ flex: 1,
193
+ justifyContent: 'center',
194
+ }}
195
+ >
196
+ {!state.selectedSkill.length ? <Text style={{ marginLeft: cx(10) }}>{I18n.getLang('timer_ceiling_fan_selectionfield_no_components_text')}</Text> :
197
+ <View
198
+ style={{
199
+ marginHorizontal: cx(8),
200
+ marginTop: cx(8),
201
+ borderRadius: 4,
202
+ }}
203
+ >
204
+ <FlatList
205
+ data={state.selectedSkill}
206
+ renderItem={item => renderItem(item)}
207
+ keyExtractor={(item: dpItem) => item.dpId}
208
+ />
209
+ </View>}
210
+ </View>
211
+ {state.skillList.map((skill: dpItem) => {
212
+ return (
213
+ <TouchableOpacity style={styles.skillListItem} key={skill.dpId} onPress={() => handelTimer(skill, true)}>
214
+ <Text style={{ color: '#000' }}>{skill.label}</Text>
215
+ <Image style={{ width: cx(16), height: cx(16) }} source={res.device_panel_timer_add} />
216
+ </TouchableOpacity>
217
+ )
218
+ })}
219
+ </View>
220
+
221
+ {/* Start */}
222
+ <View
223
+ style={{
224
+ height: cx(50),
225
+ marginVertical: cx(20),
226
+ borderRadius: cx(8),
227
+ backgroundColor: !state.selectedSkill.length ? '#FFE0D4' : '#f60',
228
+ }}
229
+ >
230
+ <TouchableOpacity
231
+ style={{
232
+ flex: 1,
233
+ justifyContent: 'center',
234
+ alignItems: 'center',
235
+ }}
236
+ onPress={onStartPress}
237
+ >
238
+ <Text style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>
239
+ {I18n.getLang('timer_sockets_button_text')}
240
+ </Text>
241
+ </TouchableOpacity>
242
+ </View></>}
243
+
244
+ {/* Active timer */}
245
+ {!!runningTimer().length && (
246
+ showActiveView() ? <>
247
+ <Text style={styles.itemTitle}>{I18n.formatValue('timer_nightplug_active_timer_subheadline2_text', runningTimer().length.toString())}</Text>
248
+ {runningTimer().map(timer => (
249
+ <View key={timer.dpId}>
250
+ <Card containerStyle={styles.activeTimer}>
251
+ <View style={styles.activeTimerTitleBox}>
252
+ <Text style={styles.activeTimerTitle}>{timer.label}</Text>
253
+ <TouchableOpacity onPress={() => { timer.countdown[1](0).then() }} style={styles.activeTimerCancelBtn}>
254
+ <Text style={{ color: '#fff', fontSize: cx(12) }}>{I18n.getLang('auto_scan_system_cancel')}</Text>
255
+ </TouchableOpacity>
256
+ </View>
257
+ <View style={styles.activeTimerTimeBox}>
258
+ <Progress.Double
259
+ style={{
260
+ width: 35,
261
+ height: 35,
262
+ // backgroundColor: 'red',
263
+ justifyContent: 'center',
264
+ alignItems: 'center',
265
+ }}
266
+ disabled={true}
267
+ minValue={0}
268
+ maxValue={getProgressByDp(timer.dpId)[0]}
269
+ startDegree={270}
270
+ andDegree={360}
271
+ scaleHeight={2}
272
+ thumbStrokeWidth={2}
273
+ thumbRadius={0}
274
+ />
275
+ <Text style={{ marginLeft: cx(20), fontSize: cx(22) }}>{getActiveTimeString(timer.countdown[0])}</Text>
276
+ </View>
277
+ <Text>{I18n.formatValue(timer.enable[0] ? timer.stringOff : timer.stringOn, getSingleLightEndTimeString(timer.countdown[0]))}</Text>
278
+ </Card>
279
+ <Spacer height={cx(40)} />
280
+ </View>
281
+ ))}
282
+ </> :
283
+ <View
284
+ style={{
285
+ flexDirection: 'column',
286
+ alignItems: 'center',
287
+ marginVertical: cx(30),
288
+ }}
289
+ >
290
+ <Progress.Double
291
+ style={{
292
+ width: 172,
293
+ height: 172,
294
+ justifyContent: 'center',
295
+ alignItems: 'center',
296
+ }}
297
+ minValue={0}
298
+ maxValue={getProgressByDp(runningTimer()[0].dpId)[0]}
299
+ startDegree={270}
300
+ andDegree={360}
301
+ disabled={true}
302
+ foreColor={'#f60'}
303
+ scaleHeight={14}
304
+ minThumbFill={'#f60'}
305
+ minThumbStroke={'#f60'}
306
+ thumbStrokeWidth={0}
307
+ thumbRadius={14 / 2 - 0.5}
308
+ thumbFill={'#f60'}
309
+ thumbStroke={'#f60'}
310
+ renderCenterView={
311
+ <View style={{ position: 'absolute' }}>
312
+ <Text style={{ fontSize: cx(22), fontWeight: 'bold', color: '#666' }}>
313
+ {getActiveTimeString(runningTimer()[0].countdown[0])}
314
+ </Text>
315
+ </View>
316
+ }
317
+ />
318
+ <View style={{ marginVertical: cx(30) }}>
319
+ <Text>
320
+ {I18n.formatValue(
321
+ runningTimer()[0].enable ?
322
+ runningTimer()[0].stringOff :
323
+ runningTimer()[0].stringOn, getSingleLightEndTimeString(runningTimer()[0].countdown[0])
324
+ )}
325
+ </Text>
326
+ </View>
327
+ <View>
328
+ <View
329
+ style={{
330
+ width: cx(75.2),
331
+ height: cx(36),
332
+ borderRadius: 5.8,
333
+ backgroundColor: '#666',
334
+ }}
335
+ >
336
+ <TouchableOpacity
337
+ style={{
338
+ flex: 1,
339
+ justifyContent: 'center',
340
+ alignItems: 'center',
341
+ }}
342
+ onPress={() => runningTimer()[0].countdown[1](0).then()}
343
+ >
344
+ <Text style={{ fontSize: 12.2, fontWeight: 'bold', color: '#fff' }}>
345
+ {I18n.getLang('auto_scan_system_cancel')}
346
+ </Text>
347
+ </TouchableOpacity>
348
+ </View>
349
+ </View>
350
+ </View>
351
+ )}
352
+ </View>
353
+ </ScrollView>
354
+ </Page>
355
+ )
356
+ }
357
+
358
+ const styles = StyleSheet.create({
359
+ content: {
360
+ marginHorizontal: cx(24)
361
+ },
362
+ itemTitle: {
363
+ color: '#000',
364
+ fontSize: cx(16),
365
+ fontWeight: 'bold',
366
+ fontFamily: 'helvetica_neue_lt_std_bd',
367
+ marginTop: cx(30),
368
+ marginBottom: cx(10)
369
+ },
370
+ skillListItem: {
371
+ flexDirection: 'row',
372
+ justifyContent: 'space-between',
373
+ height: cx(30),
374
+ alignItems: 'center',
375
+ marginVertical: cx(5)
376
+ },
377
+ activeTimer: {
378
+ marginHorizontal: cx(20),
379
+ marginVertical: cx(20),
380
+ },
381
+ activeTimerTitleBox: {
382
+ flexDirection: 'row',
383
+ justifyContent: 'space-between',
384
+ alignItems: 'center',
385
+ marginBottom: cx(20),
386
+ },
387
+ activeTimerTitle: {
388
+ fontSize: cx(20),
389
+ color: '#000',
390
+ },
391
+ activeTimerCancelBtn: {
392
+ paddingHorizontal: cx(8),
393
+ paddingVertical: cx(5),
394
+ alignItems: 'center',
395
+ justifyContent: 'center',
396
+ backgroundColor: '#666',
397
+ borderRadius: cx(5)
398
+ },
399
+ activeTimerTimeBox: {
400
+ flexDirection: 'row',
401
+ alignItems: 'center',
402
+ marginBottom: cx(20),
403
+ },
404
+ })
405
+
406
+ export default TimerPage
@@ -0,0 +1,91 @@
1
+ import { useDeviceId, useDp } from "@ledvance/base/src/models/modules/NativePropsSlice"
2
+ import { Result } from "@ledvance/base/src/models/modules/Result"
3
+ import { dpItem } from "./TimerPage"
4
+ import { useCountDown as useCountDownAHook } from 'ahooks'
5
+ import dayjs from "dayjs"
6
+ import { NativeApi } from "@ledvance/base/src/api/native"
7
+ import { useEffect, useState } from "react"
8
+
9
+ export const useCountdowns= (dps: dpItem[]) =>{
10
+ return dps.map(dp => {
11
+ return ({
12
+ countdown: useDp<number,(value:number)=>Promise<Result<any>>>(dp.dpId),
13
+ enable: useDp<boolean, (value:boolean)=>Promise<Result<any>>>(dp.enableDp),
14
+ ...dp
15
+ })
16
+ })
17
+ }
18
+
19
+
20
+ const useFormateProgress : (dp: dpItem, func: Function) => [number, (time: number, t: any) => void]= (dp, cancelTimer) =>{
21
+ const [progress, setProgress] = useCountDownAHook({
22
+ interval: 1000,
23
+ onEnd: () => cancelTimer(dp.dpId),
24
+ })
25
+ const [countdown] = useDp<number,(value:number)=>Promise<Result<any>>>(dp.dpId)
26
+ const devId = useDeviceId()
27
+ const [cloudProgressNumber, setCloudProgressNumber] = useState(0)
28
+ const [progressNumber, setProgressNumber] = useState(0)
29
+ const [cloudData, setCloudData] = useState<any>()
30
+ const getCloudJson = () =>{
31
+ NativeApi.getJson(devId, dp.cloudKey).then(res =>{
32
+ if(res.success && res.data){
33
+ const result = JSON.parse(res.data)
34
+ setCloudData(result)
35
+ setCloudProgressNumber(result.progressAllNumber)
36
+ if(countdown > 0) setProgress(Date.now() + countdown * 1000)
37
+ }
38
+ })
39
+ }
40
+ useEffect(() =>{
41
+ getCloudJson()
42
+ }, [])
43
+
44
+ useEffect(() =>{
45
+ if(cloudProgressNumber > 0){
46
+ const currentTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
47
+ const remainingTime = Number(cloudData && dayjs(cloudData[getKey('StartTime', dp.dpId)] || 0).diff(currentTime))
48
+ const time = (cloudData && cloudData[getKey('Status', dp.dpId)] && remainingTime >= -1000) ? remainingTime : Number(progress.toString().slice(0, -3) + "000")
49
+ const conversion = (time / 1000) / cloudProgressNumber * 100
50
+ setProgressNumber(!isNaN(conversion) ? conversion : 0)
51
+ }
52
+ }, [progress])
53
+
54
+ useEffect(() =>{
55
+ if(countdown === 0 && cloudData){
56
+ setProgress(0)
57
+ setProgressNumber(0)
58
+ setCloudData('')
59
+ NativeApi.putJson(devId, dp.cloudKey, JSON.stringify({ [getKey('Status', dp.dpId)]: false, [getKey('StartTime', dp.dpId)]: '', progressAllNumber: 0 }))
60
+ }
61
+
62
+ if(countdown === 0 && !cloudData){
63
+ setProgress(0)
64
+ setProgressNumber(0)
65
+ }
66
+
67
+ if(countdown > 0 && !cloudData){
68
+ getCloudJson()
69
+ }
70
+ }, [countdown, cloudData])
71
+
72
+ const getKey = (suffix:string, dpId:string) =>{
73
+ const key = dpId + suffix
74
+ return key
75
+ }
76
+
77
+ const startTimer = (time: number, t) => {
78
+ setCloudProgressNumber(time)
79
+ setProgress(Date.now() + time * 1000)
80
+ NativeApi.putJson(devId, dp.cloudKey, JSON.stringify({ [getKey('Status', dp.dpId)]: true, [getKey('StartTime', dp.dpId)]: t, progressAllNumber: time }));
81
+ }
82
+
83
+ return [progressNumber, startTimer]
84
+ }
85
+
86
+ export const useProgress = (dps: dpItem[], cancelTimer) =>{
87
+ return dps.map(dp => ({
88
+ progressHook: useFormateProgress(dp, cancelTimer),
89
+ ...dp
90
+ }))
91
+ }