@ledvance/ui-biz-bundle 1.1.18 → 1.1.20

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.1.18",
7
+ "version": "1.1.20",
8
8
  "scripts": {},
9
9
  "dependencies": {
10
10
  "@ledvance/base": "^1.x",
@@ -65,7 +65,7 @@ const FlagEditPage = () => {
65
65
  }, [state.currentNode, state.currentWhiteNode])
66
66
 
67
67
  const nameRepeat = useMemo(() => {
68
- return !!find(params.moods, m => (m.id !== state.mood.id && m.name === state.mood.name))
68
+ return !!find(params.moods, m => (m.id !== state.mood.id && m.name === state.mood.name && state.mood.name !== ''))
69
69
  }, [state.mood.name])
70
70
 
71
71
  return (
@@ -300,7 +300,7 @@ export const defFlagList: FlagUiInfo[] = [
300
300
  version: 0,
301
301
  mode: SceneNodeTransitionMode.Jump,
302
302
  speed: 70,
303
- name: '',
303
+ name: I18n.getLang('flag_leverkusen'),
304
304
  icon: res.leverkusen_icon,
305
305
  whiteColors: [
306
306
  { brightness: 100, colorTemp: 0},
@@ -1,381 +1,362 @@
1
- import React, { useCallback, 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, useUpdateEffect } 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'
1
+ import React, { useEffect } from 'react'
2
+ import { FlatList, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
9
3
  import I18n from '@ledvance/base/src/i18n'
10
- import Page from "@ledvance/base/src/components/Page";
4
+ import Page from '@ledvance/base/src/components/Page'
5
+ import { Utils } from 'tuya-panel-kit'
6
+ import { useDeviceInfo } from '@ledvance/base/src/models/modules/NativePropsSlice'
11
7
  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 { useProgress } from "./TimerPageAction";
15
- import { cloneDeep } from "lodash";
16
- import dayjs from "dayjs";
17
- import DeleteButton from "@ledvance/base/src/components/DeleteButton";
18
-
19
- export type dpItem = {
20
- label: string
21
- value: string
22
- dpId: string
23
- enableDp: string
24
- cloudKey: any
25
- stringOn: any
26
- stringOff: any
27
- }
28
-
29
- type TimerPageRouteParams = {
30
- params: { dps: dpItem[] }
31
- }
32
-
33
- type Props = {
34
- route: RouteProp<TimerPageRouteParams, 'params'>
35
- }
8
+ import { useReactive } from 'ahooks'
9
+ import Spacer from '@ledvance/base/src/components/Spacer'
10
+ import DeleteButton from '@ledvance/base/src/components/DeleteButton'
11
+ import { TaskStatus, timeFormat, TimerPageParams, TimerTask, useTimerTasks } from './TimerAction'
12
+ import { cloneDeep, groupBy } from 'lodash'
13
+ import { useParams } from '@ledvance/base/src/hooks/Hooks'
14
+ import GroupBizRes from '../../res/GroupBizRes'
15
+ import dayjs from 'dayjs'
16
+ import Card from '@ledvance/base/src/components/Card'
17
+ import TextButton from '@ledvance/base/src/components/TextButton'
18
+ import { CircularProgress } from '@ledvance/base/src/components/CircularProgress'
36
19
 
37
20
  const { convertX: cx } = Utils.RatioUtils
21
+
38
22
  const TimerPage = () => {
39
23
  const devInfo = useDeviceInfo()
40
- const navigation = useNavigation()
41
- const { dps } = useRoute<Props['route']>().params
42
- const progress = useProgress(dps)
24
+
25
+ const params = useParams<TimerPageParams>()
26
+ const [timerTasks, setTimerTasks, changeTimerTasks] = useTimerTasks(params.timerSettableDps)
27
+
43
28
  const state = useReactive({
44
29
  hour: '00',
45
- minute: '01',
46
- skillList: [] as dpItem[],
47
- selectedSkill: [] as dpItem[]
30
+ min: '01',
31
+ tasks: cloneDeep(timerTasks),
32
+ loading: false,
48
33
  })
49
34
 
50
-
51
- const hasNotRunningTimer = () => {
52
- return !!progress.find(p => p.progressHook.countdown === 0)
53
- }
54
-
55
- const runningTimer = () => {
56
- return progress.filter(p => p.progressHook.countdown > 0)
57
- }
58
-
59
- const getProgressByDp = useCallback((dpId: string) => {
60
- return progress.find(p => p.dpId === dpId)!.progressHook
61
- }, [progress])
62
-
63
35
  useEffect(() => {
64
- if (progress.length > 1) {
65
- state.skillList = progress.filter(p => p.progressHook.countdown === 0)
66
- } else {
67
- state.skillList = []
68
- state.selectedSkill = cloneDeep(dps)
69
- }
70
- }, [runningTimer().length])
36
+ state.tasks = cloneDeep(timerTasks)
37
+ .map(task => {
38
+ const newStatus = params.timerSettableDps.length === 1 && task.status !== TaskStatus.Started
39
+ ? TaskStatus.Selected : task.status
40
+ return { ...task, status: newStatus }
41
+ })
42
+ }, [JSON.stringify(timerTasks)])
71
43
 
72
- useUpdateEffect(() => {
73
- if (state.hour === '00' && state.minute === '00') {
74
- state.minute = '01'
75
- }
76
- }, [state.hour, state.minute])
44
+ const tasksByStatus = groupBy(state.tasks, 'status')
77
45
 
78
-
79
- const handelTimer = (timer: dpItem, isAdd: boolean) => {
80
- if (isAdd) {
81
- state.selectedSkill = [...state.selectedSkill, timer]
82
- state.skillList = state.skillList.filter(item => item.dpId !== timer.dpId)
83
- } else {
84
- state.selectedSkill = state.selectedSkill.filter(item => item.dpId !== timer.dpId)
85
- state.skillList = [...state.skillList, timer]
86
- }
87
- }
88
-
89
- const onStartPress = async () => {
90
- let mCountdown = 0
91
- const hour = parseInt(state.hour)
92
- const minute = parseInt(state.minute)
93
- if (hour > 0) {
94
- mCountdown += hour * 60 * 60
95
- }
96
- if (minute > 0) {
97
- mCountdown += minute * 60
98
- }
99
- if (!state.selectedSkill.length || mCountdown === 0) return
100
- for (let skill of state.selectedSkill) {
101
- const time = dayjs()
102
- .add(Number(hour), 'h')
103
- .add(Number(minute), 'm')
104
- .format('YYYY-MM-DD HH:mm:ss');
105
- await getProgressByDp(skill.dpId).startTimer(mCountdown, time)
106
- }
107
- state.selectedSkill = []
108
- }
109
-
110
- const getActiveTimeString = (c: number) => {
111
- const h = parseInt(String(c / 3600))
112
- const m = Number(((c - 3600 * h) / 60).toFixed(0))
113
- if (h > 0) {
114
- if (m === 0) {
115
- return h + ' h'
116
- } else {
117
- return h + ' h ' + m + ' min'
118
- }
119
- } else {
120
- return m + ' min'
121
- }
122
- }
123
-
124
- const getSingleLightEndTimeString = (mCountdown: number) => {
125
- const date = new Date()
126
- const now = date.getTime()
127
- const after = new Date(now + mCountdown * 1000)
128
- const timeString = `${after.getHours()}:${after.getMinutes().toString().padStart(2, '0')}`
129
- return timeString
130
- }
131
-
132
- const showActiveView = () => {
133
- return progress.length > 1
134
- }
135
-
136
- const renderItem = ({ item }) => {
137
- return (
138
- <View style={{
139
- flexDirection: 'row',
140
- justifyContent: 'space-between',
141
- alignItems: 'center',
142
- backgroundColor: '#fff',
143
- marginBottom: cx(8)
144
- }}>
145
- <Text
146
- style={{
147
- color: '#000',
148
- fontSize: 14,
149
- marginHorizontal: cx(6),
150
- marginVertical: cx(9),
151
- }}
152
- >
153
- {item.label}
154
- </Text>
155
- {progress.length > 1 && <TouchableOpacity onPress={() => handelTimer(item, false)}>
156
- <Image style={{ width: cx(16), height: cx(16), marginRight: cx(5) }} source={res.ic_arrows_nav_clear} />
157
- </TouchableOpacity>}
158
- </View>
159
- );
160
- };
46
+ const noSelectedTasks = tasksByStatus[TaskStatus.NoSelected] || []
47
+ const selectedTasks = tasksByStatus[TaskStatus.Selected] || []
48
+ const startedTasks = tasksByStatus[TaskStatus.Started] || []
49
+ const startEnable = selectedTasks.length > 0
161
50
 
162
51
  return (
163
52
  <Page
164
53
  backText={devInfo.name}
165
54
  headlineText={I18n.getLang('timer_nightplug_headline_text')}
166
- onBackClick={navigation.goBack}
167
- >
55
+ loading={state.loading}>
168
56
  <ScrollView
57
+ style={styles.root}
169
58
  nestedScrollEnabled={true}
170
59
  showsHorizontalScrollIndicator={false}
171
- showsVerticalScrollIndicator={false}
172
- >
173
- {/* picker */}
174
- <View style={styles.content}>
175
- {hasNotRunningTimer() && <>
176
- <LdvPickerView
177
- hour={state.hour}
178
- minute={state.minute}
179
- setHour={h => state.hour = h}
180
- setMinute={m => state.minute = m}
181
- unit={['h', 'min']}
182
- />
183
- {/* Apply for */}
184
- <View>
185
- <Text style={styles.itemTitle}>{I18n.getLang('timeschedule_add_schedule_subheadline_text')}</Text>
186
- <View
187
- style={{
188
- backgroundColor: '#f6f6f6',
189
- borderRadius: 4,
190
- minHeight: cx(50),
191
- flex: 1,
192
- justifyContent: 'center',
193
- }}
194
- >
195
- {!state.selectedSkill.length ? <Text style={{ marginLeft: cx(10) }}>{I18n.getLang('timer_ceiling_fan_selectionfield_no_components_text')}</Text> :
196
- <View
197
- style={{
198
- marginHorizontal: cx(8),
199
- marginTop: cx(8),
200
- borderRadius: 4,
201
- }}
202
- >
203
- <FlatList
204
- data={state.selectedSkill}
205
- renderItem={item => renderItem(item)}
206
- keyExtractor={(item: dpItem) => item.dpId}
207
- />
208
- </View>}
60
+ showsVerticalScrollIndicator={false}>
61
+ <View>
62
+ {startedTasks.length !== state.tasks.length &&
63
+ <View style={styles.content}>
64
+ <LdvPickerView
65
+ hour={state.hour}
66
+ minute={state.min}
67
+ unit={['h', 'min']}
68
+ setHour={hour => {
69
+ state.hour = hour
70
+ }}
71
+ setMinute={min => {
72
+ state.min = min
73
+ }}/>
74
+ <Spacer height={cx(30)}/>
75
+ <Text style={styles.applyFor}>{I18n.getLang('timeschedule_add_schedule_subheadline_text')}</Text>
76
+ <Spacer height={cx(10)}/>
77
+ <FlatList
78
+ data={selectedTasks}
79
+ style={styles.taskList}
80
+ renderItem={({ item }) => {
81
+ return (
82
+ <View style={styles.taskItem}>
83
+ <Text style={styles.taskItemText}>{item.name}</Text>
84
+ {state.tasks.length > 1 &&
85
+ <TouchableOpacity
86
+ onPress={() => {
87
+ item.status = TaskStatus.NoSelected
88
+ changeTimerTasks([cloneDeep(item)])
89
+ }}>
90
+ <Image style={styles.taskItemIcon} source={GroupBizRes.ic_arrows_nav_clear}/>
91
+ </TouchableOpacity>}
92
+ </View>
93
+ )
94
+ }}
95
+ keyExtractor={item => item.dp.key}
96
+ ListEmptyComponent={() => <View style={styles.listEmptyView}>
97
+ <Text style={styles.listEmptyText}>
98
+ {I18n.getLang('timer_ceiling_fan_selectionfield_no_components_text')}
99
+ </Text>
100
+ </View>}/>
101
+ <Spacer height={cx(6)}/>
102
+ <FlatList
103
+ data={noSelectedTasks}
104
+ style={styles.noSelectTaskList}
105
+ renderItem={({ item }) => {
106
+ return (
107
+ <TouchableOpacity
108
+ style={styles.noSelectTaskItem}
109
+ onPress={() => {
110
+ item.status = TaskStatus.Selected
111
+ changeTimerTasks([cloneDeep(item)])
112
+ }}>
113
+ <Spacer width={cx(8)}/>
114
+ <Text style={styles.noSelectTaskText}>{item.name}</Text>
115
+ <Image style={styles.taskItemIcon} source={GroupBizRes.device_panel_timer_add}/>
116
+ </TouchableOpacity>
117
+ )
118
+ }}
119
+ keyExtractor={item => item.dp.key}/>
120
+ <Spacer/>
121
+ <DeleteButton
122
+ text={I18n.getLang('timer_sockets_button_text')}
123
+ disabled={selectedTasks.length === 0}
124
+ onPress={async () => {
125
+ state.loading = true
126
+ const tasks = selectedTasks.map(task => {
127
+ return {
128
+ ...task,
129
+ startTime: dayjs().unix(),
130
+ duration: (parseInt(state.hour) * 60 + parseInt(state.min)) * 60,
131
+ status: TaskStatus.Started,
132
+ }
133
+ })
134
+ tasks.push(...cloneDeep(startedTasks))
135
+ await setTimerTasks(cloneDeep(tasks))
136
+ state.loading = false
137
+ }}
138
+ textStyle={{ fontSize: cx(14) }}
139
+ style={{ backgroundColor: !startEnable ? '#FFE0D4' : '#f60' }}/>
209
140
  </View>
210
- {state.skillList.map((skill: dpItem) => {
211
- return (
212
- <TouchableOpacity style={styles.skillListItem} key={skill.dpId} onPress={() => handelTimer(skill, true)}>
213
- <Text style={{ color: '#000' }}>{skill.label}</Text>
214
- <Image style={{ width: cx(16), height: cx(16) }} source={res.device_panel_timer_add} />
215
- </TouchableOpacity>
216
- )
217
- })}
218
- </View>
219
-
220
- {/* Start */}
221
- <Spacer />
222
- <DeleteButton
223
- text={I18n.getLang('timer_sockets_button_text')}
224
- onPress={onStartPress}
225
- textStyle={{ fontSize: cx(14) }}
226
- style={{ backgroundColor: !state.selectedSkill.length ? '#FFE0D4' : '#f60' }}
227
- />
228
- <Spacer />
229
- </>}
230
-
231
- {/* Active timer */}
232
- {!!runningTimer().length && (
233
- showActiveView() ? <>
234
- <Text style={styles.itemTitle}>{I18n.formatValue('timer_nightplug_active_timer_subheadline2_text', runningTimer().length.toString())}</Text>
235
- {runningTimer().map(timer => (
236
- <View key={timer.dpId}>
237
- <Card containerStyle={styles.activeTimer}>
238
- <View style={styles.activeTimerTitleBox}>
239
- <Text style={styles.activeTimerTitle}>{timer.label}</Text>
240
- <TouchableOpacity
241
- onPress={() => {
242
- getProgressByDp(timer.dpId).endTimer()
243
- }}
244
- style={styles.activeTimerCancelBtn}>
245
- <Text style={{ color: '#fff', fontSize: cx(12) }}>{I18n.getLang('auto_scan_system_cancel')}</Text>
246
- </TouchableOpacity>
247
- </View>
248
- <View style={styles.activeTimerTimeBox}>
249
- <Progress.Double
250
- style={{
251
- width: 35,
252
- height: 35,
253
- // backgroundColor: 'red',
254
- justifyContent: 'center',
255
- alignItems: 'center',
256
- }}
257
- disabled={true}
258
- minValue={0}
259
- maxValue={getProgressByDp(timer.dpId).progressNumber}
260
- startDegree={270}
261
- andDegree={360}
262
- scaleHeight={2}
263
- thumbStrokeWidth={2}
264
- thumbRadius={0}
265
- />
266
- <Text style={{ marginLeft: cx(20), fontSize: cx(22) }}>{getActiveTimeString(timer.progressHook.countdown)}</Text>
267
- </View>
268
- <Text>{I18n.formatValue(timer.enable ? timer.stringOff : timer.stringOn, getSingleLightEndTimeString(timer.progressHook.countdown))}</Text>
269
- </Card>
270
- <Spacer height={cx(40)} />
271
- </View>
272
- ))}
273
- </> :
274
- <View
275
- style={{
276
- flexDirection: 'column',
277
- alignItems: 'center',
278
- marginVertical: cx(30),
279
- }}
280
- >
281
- <Progress.Double
282
- style={{
283
- width: 172,
284
- height: 172,
285
- justifyContent: 'center',
286
- alignItems: 'center',
287
- }}
288
- minValue={0}
289
- maxValue={getProgressByDp(runningTimer()[0].dpId).progressNumber}
290
- startDegree={270}
291
- andDegree={360}
292
- disabled={true}
293
- foreColor={'#f60'}
294
- scaleHeight={14}
295
- minThumbFill={'#f60'}
296
- minThumbStroke={'#f60'}
297
- thumbStrokeWidth={0}
298
- thumbRadius={14 / 2 - 0.5}
299
- thumbFill={'#f60'}
300
- thumbStroke={'#f60'}
301
- renderCenterView={
302
- <View style={{ position: 'absolute' }}>
303
- <Text style={{ fontSize: cx(22), fontWeight: 'bold', color: '#666' }}>
304
- {getActiveTimeString(runningTimer()[0].progressHook.countdown)}
305
- </Text>
306
- </View>
307
- }
308
- />
309
- <View style={{ marginVertical: cx(30) }}>
310
- <Text>
311
- {I18n.formatValue(
312
- runningTimer()[0].enable ?
313
- runningTimer()[0].stringOff :
314
- runningTimer()[0].stringOn, getSingleLightEndTimeString(runningTimer()[0].progressHook.countdown)
315
- )}
316
- </Text>
317
- </View>
318
- <DeleteButton
319
- text={I18n.getLang('auto_scan_system_cancel')}
320
- style={{ paddingHorizontal: cx(15), width: 'auto', height: cx(40) }}
321
- textStyle={{ fontSize: cx(14) }}
322
- onPress={() => {
323
- getProgressByDp(runningTimer()[0].dpId).endTimer()
324
- }}
325
- />
141
+ }
142
+ {startedTasks.length > 0 && params.timerSettableDps.length > 1 &&
143
+ <>
144
+ <Spacer height={cx(30)}/>
145
+ <View style={styles.content}>
146
+ <Text style={styles.applyFor}>{
147
+ I18n.formatValue('timer_nightplug_active_timer_subheadline2_text', startedTasks.length.toString())
148
+ }</Text>
149
+ </View>
150
+ <FlatList
151
+ data={startedTasks}
152
+ renderItem={({ item }) => {
153
+ return ActiveTimerItem(item, async () => {
154
+ state.loading = true
155
+ const tasks = cloneDeep(startedTasks)
156
+ tasks.forEach(task => {
157
+ if (task.dp.code === item.dp.code) {
158
+ task.status = TaskStatus.NoSelected
159
+ task.startTime = 0
160
+ task.duration = 0
161
+ }
162
+ })
163
+ await setTimerTasks(tasks)
164
+ state.loading = false
165
+ })
166
+ }}
167
+ keyExtractor={item => item.dp.key}
168
+ ListHeaderComponent={() => (<Spacer height={cx(10)}/>)}
169
+ ItemSeparatorComponent={() => (<Spacer height={cx(10)}/>)}
170
+ ListFooterComponent={() => (<Spacer height={cx(20)}/>)}/>
171
+ </>
172
+ }
173
+ {
174
+ startedTasks.length === 1 && params.timerSettableDps.length === 1 &&
175
+ <View style={{ justifyContent: 'center', marginHorizontal: cx(24) }}>
176
+ <Spacer height={cx(116)}/>
177
+ <View
178
+ style={{
179
+ justifyContent: 'center',
180
+ alignItems: 'center',
181
+ }}>
182
+ <CircularProgress
183
+ progress={(startedTasks[0].timeLeft / startedTasks[0].duration) * 100}
184
+ size={cx(172)}
185
+ strokeWidth={cx(14)}>
186
+ <Text style={styles.activeTaskTimeText2}>{timeFormat(startedTasks[0].timeLeft)}</Text>
187
+ </CircularProgress>
188
+ </View>
189
+ <Spacer height={cx(32)}/>
190
+ <Text
191
+ style={[styles.activeTaskDesc, { textAlign: 'center' }]}>{I18n.formatValue(startedTasks[0].dp.enable ? startedTasks[0].stringOff : startedTasks[0].stringOn, timeFormat(startedTasks[0].timeLeft))}</Text>
192
+ <Spacer height={cx(32)}/>
193
+ <TextButton
194
+ text={I18n.getLang('auto_scan_system_cancel')}
195
+ style={styles.activeTasBtn}
196
+ textStyle={styles.activeTaskBtnText}
197
+ onPress={async () => {
198
+ state.loading = true
199
+ const task = cloneDeep(startedTasks[0])
200
+ task.status = TaskStatus.NoSelected
201
+ task.startTime = 0
202
+ task.duration = 0
203
+ await setTimerTasks([task])
204
+ state.loading = false
205
+ }}/>
206
+ <Spacer width={cx(20)}/>
326
207
  </View>
327
- )}
208
+ }
209
+ <Spacer/>
328
210
  </View>
329
211
  </ScrollView>
330
212
  </Page>
331
213
  )
332
214
  }
333
215
 
216
+ function ActiveTimerItem(task: TimerTask, onCancel: () => void) {
217
+ return (
218
+ <Card style={{ marginHorizontal: cx(24) }}>
219
+ <Spacer/>
220
+ <View style={styles.activeTaskHeadLine}>
221
+ <Spacer width={cx(20)}/>
222
+ <Text style={styles.activeTaskHeadLineText}>{task.name}</Text>
223
+ <TextButton
224
+ text={I18n.getLang('auto_scan_system_cancel')}
225
+ style={styles.activeTaskHeadLineBtn}
226
+ textStyle={styles.activeTaskHeadLineBtnText}
227
+ onPress={onCancel}/>
228
+ <Spacer width={cx(12)}/>
229
+ </View>
230
+ <Spacer height={cx(12)}/>
231
+ <View style={styles.activeTaskHeadLine}>
232
+ <Spacer width={cx(20)}/>
233
+ <CircularProgress
234
+ progress={(task.timeLeft / task.duration) * 100}
235
+ size={cx(24)}
236
+ strokeWidth={cx(2)}/>
237
+ <Spacer width={cx(16)}/>
238
+ <Text style={styles.activeTaskTimeText}>{timeFormat(task.timeLeft)}</Text>
239
+ <Spacer width={cx(20)}/>
240
+ </View>
241
+ <Spacer/>
242
+ <View style={{ marginHorizontal: cx(20) }}>
243
+ <Text style={styles.activeTaskDesc}>{
244
+ I18n.formatValue(task.dp.enable ? task.stringOff : task.stringOn, timeFormat(task.timeLeft))
245
+ }</Text>
246
+ </View>
247
+ <Spacer height={cx(22)}/>
248
+ </Card>
249
+ )
250
+ }
251
+
334
252
  const styles = StyleSheet.create({
253
+ root: {
254
+ flex: 1,
255
+ },
335
256
  content: {
336
- marginHorizontal: cx(24)
257
+ marginHorizontal: cx(24),
337
258
  },
338
- itemTitle: {
259
+ applyFor: {
339
260
  color: '#000',
340
261
  fontSize: cx(16),
341
262
  fontWeight: 'bold',
342
263
  fontFamily: 'helvetica_neue_lt_std_bd',
343
- marginTop: cx(30),
344
- marginBottom: cx(10)
345
264
  },
346
- skillListItem: {
265
+ taskList: {
266
+ backgroundColor: '#f6f6f6',
267
+ paddingTop: cx(8),
268
+ borderRadius: cx(4),
269
+ },
270
+ taskItem: {
271
+ marginHorizontal: cx(8),
272
+ marginBottom: cx(8),
347
273
  flexDirection: 'row',
348
274
  justifyContent: 'space-between',
349
- height: cx(30),
350
275
  alignItems: 'center',
351
- marginVertical: cx(5)
276
+ backgroundColor: '#fff',
352
277
  },
353
- activeTimer: {
354
- marginHorizontal: cx(20),
355
- marginVertical: cx(20),
278
+ taskItemText: {
279
+ color: '#000',
280
+ fontSize: 14,
281
+ marginHorizontal: cx(8),
282
+ marginVertical: cx(9),
356
283
  },
357
- activeTimerTitleBox: {
284
+ taskItemIcon: {
285
+ width: cx(16),
286
+ height: cx(16),
287
+ marginRight: cx(8),
288
+ },
289
+ listEmptyView: {
290
+ marginHorizontal: cx(8),
291
+ marginTop: cx(6),
292
+ marginBottom: cx(14),
293
+ },
294
+ listEmptyText: {
295
+ color: '#666',
296
+ fontSize: cx(14),
297
+ },
298
+ noSelectTaskList: {},
299
+ noSelectTaskItem: {
300
+ height: cx(30),
358
301
  flexDirection: 'row',
359
- justifyContent: 'space-between',
360
302
  alignItems: 'center',
361
- marginBottom: cx(20),
362
303
  },
363
- activeTimerTitle: {
364
- fontSize: cx(20),
304
+ noSelectTaskText: {
305
+ flex: 1,
365
306
  color: '#000',
307
+ fontSize: cx(14),
366
308
  },
367
- activeTimerCancelBtn: {
368
- paddingHorizontal: cx(8),
369
- paddingVertical: cx(5),
309
+ activeTaskHeadLine: {
310
+ flexDirection: 'row',
370
311
  alignItems: 'center',
371
- justifyContent: 'center',
312
+ },
313
+ activeTaskHeadLineText: {
314
+ flex: 1,
315
+ color: '#000',
316
+ fontSize: cx(14),
317
+ },
318
+ activeTaskHeadLineBtn: {
319
+ minWidth: cx(50),
320
+ height: cx(24),
321
+ padding: 0,
372
322
  backgroundColor: '#666',
373
- borderRadius: cx(5)
323
+ borderRadius: cx(4),
374
324
  },
375
- activeTimerTimeBox: {
376
- flexDirection: 'row',
377
- alignItems: 'center',
378
- marginBottom: cx(20),
325
+ activeTaskHeadLineBtnText: {
326
+ color: '#fff',
327
+ fontSize: cx(8.2),
328
+ },
329
+ activeTaskTimeText: {
330
+ flex: 1,
331
+ color: '#666',
332
+ fontSize: cx(24),
333
+ fontWeight: 'bold',
334
+ fontFamily: 'helvetica_neue_lt_std_bd',
335
+ },
336
+ activeTaskDesc: {
337
+ flex: 1,
338
+ color: '#666',
339
+ fontSize: cx(14),
340
+ },
341
+ activeTasBtn: {
342
+ width: cx(75),
343
+ height: cx(36),
344
+ padding: 0,
345
+ backgroundColor: '#666',
346
+ borderRadius: cx(4),
347
+ },
348
+ activeTaskBtnText: {
349
+ color: '#fff',
350
+ fontSize: cx(12.2),
351
+ },
352
+ activeTaskTimeText2: {
353
+ flex: 1,
354
+ marginHorizontal: cx(20),
355
+ color: '#666',
356
+ fontSize: cx(22),
357
+ textAlign: 'center',
358
+ fontWeight: 'bold',
359
+ fontFamily: 'helvetica_neue_lt_std_bd',
379
360
  },
380
361
  })
381
362
 
@@ -1,131 +1,174 @@
1
- import { useCallback, useEffect, useState } from "react"
2
- import { useDeviceId, useDp } from "@ledvance/base/src/models/modules/NativePropsSlice"
3
- import { Result } from "@ledvance/base/src/models/modules/Result"
4
- import { dpItem } from "./TimerPage"
5
- import { useCountDown as useCountDownAHook, useUpdateEffect } from 'ahooks'
6
- import dayjs from "dayjs"
7
- import { NativeApi } from "@ledvance/base/src/api/native"
8
-
9
-
10
- export const useCountdowns = (dps: dpItem[]) => {
11
- return dps.map(dp => {
12
- return ({
13
- countdown: useDp<number, (value: number) => Promise<Result<any>>>(dp.dpId),
14
- enable: useDp<boolean, (value: boolean) => Promise<Result<any>>>(dp.enableDp),
15
- ...dp
16
- })
17
- })
1
+ import { useFeatureHook } from '@ledvance/base/src/models/modules/NativePropsSlice'
2
+ import { Result } from '@ledvance/base/src/models/modules/Result'
3
+ import dayjs from 'dayjs'
4
+ import { useCountDown, useReactive } from 'ahooks'
5
+ import { useEffect, useMemo } from 'react'
6
+
7
+ export interface TimerTask {
8
+ name: string
9
+ startTime: number
10
+ duration: number
11
+ timeLeft: number
12
+ dp: {
13
+ key: string
14
+ code: string
15
+ enable: boolean
16
+ }
17
+ status: TaskStatus
18
+ stringOn: string
19
+ stringOff: string
18
20
  }
19
21
 
20
- type FormateProgressType = {
21
- progressNumber: number
22
- countdown: number
23
- startTimer: (c: number, time: string) => Promise<void>
24
- endTimer: () => void
22
+ export enum TaskStatus {
23
+ NoSelected,
24
+ Selected,
25
+ Started
25
26
  }
26
- const useFormateProgress: (dp: dpItem) => FormateProgressType = (dp) => {
27
- const [countdown, setCountdown] = useDp<number, (value: number) => Promise<Result<any>>>(dp.dpId)
28
- const [progress, setProgress] = useCountDownAHook({
29
- interval: 1000,
30
- onEnd: () => {
31
- initTimerFn()
32
- setCountdown(0)
27
+
28
+ export interface TimerConfig {
29
+ timerTasks: TimerTask[]
30
+ }
31
+
32
+ export function useTimerTasks(timerSettableDps: TimerSettableDp[]):
33
+ [TimerTask[], (value: TimerTask[]) => Promise<Result<any>>, (tasks: TimerTask[]) => void] {
34
+ const countdowns = timerSettableDps.map(dps => {
35
+ const [countdown, setTargetDate] = useCountDown({ interval: 1000 })
36
+ return {
37
+ dpCode: dps.dp.code,
38
+ countdown: {
39
+ value: countdown,
40
+ setTargetDate,
41
+ },
42
+ }
43
+ })
44
+
45
+ const defTasks = useMemo(() => {
46
+ return timerSettableDps.map(timerSettableDp => ({
47
+ name: timerSettableDp.label,
48
+ startTime: 0,
49
+ duration: 0,
50
+ timeLeft: 0,
51
+ dp: timerSettableDp.dp,
52
+ status: TaskStatus.NoSelected,
53
+ stringOn: timerSettableDp.stringOn,
54
+ stringOff: timerSettableDp.stringOff,
55
+ }))
56
+ }, [JSON.stringify(timerSettableDps)])
57
+
58
+ const [remoteTasks, setTasks] = useFeatureHook<TimerConfig, TimerTask[]>(
59
+ 'timerTasks',
60
+ defTasks,
61
+ () => undefined,
62
+ timerTasks => {
63
+ const dps = {}
64
+ timerTasks.forEach(task => {
65
+ dps[task.dp.code] = task.duration
66
+ })
67
+ return dps
33
68
  },
69
+ )
70
+
71
+ const tasks = defTasks.map(defTask => {
72
+ const findTask = remoteTasks.find(remoteTask => remoteTask.dp.code === defTask.dp.code)
73
+ return findTask ? { ...findTask, dp: defTask.dp } : defTask
34
74
  })
35
- const devId = useDeviceId()
36
- const [cloudProgressNumber, setCloudProgressNumber] = useState(0)
37
- const [progressNumber, setProgressNumber] = useState(0)
38
- const [cloudData, setCloudData] = useState<any>()
39
- const [isTrigger, setIsTeigger] = useState(false)
40
- const getCloudJson = () => {
41
- NativeApi.getJson(devId, dp.cloudKey).then(res => {
42
- if (res.success && res.data) {
43
- const result = JSON.parse(res.data)
44
- setCloudData(result)
45
- setCloudProgressNumber(result.progressAllNumber)
46
- if (countdown > 0) setProgress(Date.now() + countdown * 1000)
47
- }
48
- })
49
- }
50
- useEffect(() => {
51
- if(countdown > 0){
52
- getCloudJson()
53
- }
54
- }, [])
75
+
76
+ const state = useReactive({ tasks: tasks })
55
77
 
56
78
  useEffect(() => {
57
- if (cloudProgressNumber > 0) {
58
- const currentTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
59
- const remainingTime = Number(cloudData && dayjs(cloudData[getKey('StartTime', dp.dpId)] || 0).diff(currentTime))
60
- const time = (cloudData && cloudData[getKey('Status', dp.dpId)] && remainingTime >= -1000) ? remainingTime : Number(progress.toString().slice(0, -3) + "000")
61
- const conversion = (time / 1000) / cloudProgressNumber * 100
62
- setProgressNumber(!isNaN(conversion) ? conversion : 0)
63
- }
64
- }, [progress])
79
+ state.tasks = tasks
80
+ .map(task => {
81
+ const taskEndTime = dayjs.unix(task.startTime)
82
+ .add(task.duration, 'second')
83
+ const taskIsEnd = task.status !== TaskStatus.Started ||
84
+ getTaskLeftover(task) === 0
65
85
 
66
- useUpdateEffect(() =>{
67
- if(countdown === 0){
68
- setIsTeigger(false)
69
- initTimerFn()
70
- }
71
- }, [countdown])
86
+ countdowns.forEach(countdown => {
87
+ if (countdown.dpCode === task.dp.code) {
88
+ countdown.countdown.setTargetDate(taskIsEnd ? undefined : taskEndTime.toDate())
89
+ }
90
+ })
72
91
 
73
- useUpdateEffect(() => {
74
- if (countdown === 0) {
75
- if(isTrigger){
76
- NativeApi.putJson(devId, dp.cloudKey, JSON.stringify({ [getKey('Status', dp.dpId)]: false, [getKey('StartTime', dp.dpId)]: '', progressAllNumber: 0 })).then()
77
- }
78
- }
92
+ return {
93
+ ...task,
94
+ startTime: taskIsEnd ? 0 : task.startTime,
95
+ duration: taskIsEnd ? 0 : task.duration,
96
+ timeLeft: taskIsEnd ? 0 : task.timeLeft,
97
+ status: taskIsEnd ? TaskStatus.NoSelected : TaskStatus.Started,
98
+ }
99
+ })
79
100
 
80
- if (countdown > 0 && !cloudData && !isTrigger) {
81
- getCloudJson()
101
+ return () => {
102
+ countdowns.forEach(countdown => {
103
+ countdown.countdown.setTargetDate(undefined)
104
+ })
82
105
  }
83
- }, [countdown, cloudData])
84
-
106
+ }, [JSON.stringify(tasks)])
85
107
 
86
-
87
- const initTimerFn = useCallback(() =>{
88
- setProgressNumber(0)
89
- setCloudData(null)
90
- setCloudProgressNumber(0)
91
- setProgress(0)
92
- }, [])
108
+ useEffect(() => {
109
+ countdowns.forEach(cd => {
110
+ state.tasks.forEach(task => {
111
+ if (task.dp.code === cd.dpCode) {
112
+ task.timeLeft = Math.trunc(cd.countdown.value / 1000)
113
+ if (task.timeLeft > 0) {
114
+ task.status = TaskStatus.Started
115
+ } else {
116
+ if (task.status === TaskStatus.Started) {
117
+ task.status = TaskStatus.NoSelected
118
+ }
119
+ }
120
+ }
121
+ })
122
+ })
123
+ }, [JSON.stringify(countdowns.map(c => c.countdown.value))])
93
124
 
94
- const getKey = (suffix: string, dpId: string) => {
95
- const key = dpId + suffix
96
- return key
125
+ const changeTasks = (timerTasks: TimerTask[]) => {
126
+ timerTasks.forEach(newTask => {
127
+ state.tasks.forEach(task => {
128
+ if (task.dp.code === newTask.dp.code) {
129
+ task.status = newTask.status
130
+ }
131
+ })
132
+ })
97
133
  }
98
134
 
99
- const startTimer = async (time: number, t:string) => {
100
- setIsTeigger(true)
101
- await setCountdown(time)
102
- const putData = { [getKey('Status', dp.dpId)]: true, [getKey('StartTime', dp.dpId)]: t, progressAllNumber: time }
103
- await NativeApi.putJson(devId, dp.cloudKey, JSON.stringify(putData))
104
- setCloudProgressNumber(time)
105
- setProgress(Date.now() + time * 1000)
106
- setCloudData(putData)
107
- }
135
+ return [state.tasks, setTasks, changeTasks]
136
+ }
108
137
 
109
- const endTimer = async () =>{
110
- setIsTeigger(false)
111
- await setCountdown(0)
112
- NativeApi.putJson(devId, dp.cloudKey, JSON.stringify({ [getKey('Status', dp.dpId)]: false, [getKey('StartTime', dp.dpId)]: '', progressAllNumber: 0 })).then(() => {
113
- initTimerFn()
114
- })
115
- }
116
-
117
- return {
118
- progressNumber,
119
- countdown,
120
- startTimer,
121
- endTimer
138
+ export function getTaskLeftover(timerTask: TimerTask): number {
139
+ // 当前时间和任务结束时间
140
+ const currentTime = dayjs()
141
+ const taskEndTime = dayjs.unix(timerTask.startTime).add(timerTask.duration, 'second')
142
+
143
+ // 剩余时间
144
+ const timeLeft = taskEndTime.diff(currentTime, 'second')
145
+
146
+ // 如果剩余时间小于0,返回0
147
+ return timeLeft > 0 ? timeLeft : 0
148
+ }
149
+
150
+ export interface TimerSettableDp {
151
+ label: string
152
+ dp: {
153
+ key: string
154
+ code: string
155
+ enable: boolean
122
156
  }
157
+ stringOn: string
158
+ stringOff: string
159
+ }
160
+
161
+ export interface TimerPageParams {
162
+ timerSettableDps: TimerSettableDp[]
123
163
  }
124
164
 
125
- export const useProgress = (dps: dpItem[]) => {
126
- return dps.map(dp => ({
127
- progressHook: useFormateProgress(dp),
128
- enable: useDp(dp.enableDp)[0] as boolean,
129
- ...dp
130
- }))
165
+ export function timeFormat(time: number): string {
166
+ let hour = Math.trunc(time / 3600)
167
+ const beforeMin = (time % 3600) / 60
168
+ let min = Math.ceil(beforeMin)
169
+ if (min === 60) {
170
+ hour += 1
171
+ min = 0
172
+ }
173
+ return `${hour > 0 ? `${hour} h ` : ''}${min > 0 ? `${min} min` : ''}`
131
174
  }