@ledvance/base 1.3.100 → 1.3.101
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/localazy.json +9 -1
- package/package.json +1 -1
- package/src/components/FeatureInfo.tsx +82 -0
- package/src/components/Page.tsx +2 -0
- package/src/components/ldvTopName.tsx +14 -6
- package/src/hooks/Hooks.ts +85 -22
- package/src/i18n/strings.ts +254 -14
- package/translateKey.txt +9 -1
package/localazy.json
CHANGED
|
@@ -1283,7 +1283,15 @@
|
|
|
1283
1283
|
"MATCH:siren_settings_antidismantle",
|
|
1284
1284
|
"MATCH:motion_detection_no_safe_mode_subheadline_text",
|
|
1285
1285
|
"MATCH:app_navigation_bottom_msg",
|
|
1286
|
-
"MATCH:internetaccess_timeschedule"
|
|
1286
|
+
"MATCH:internetaccess_timeschedule",
|
|
1287
|
+
"MATCH:chart_legend_consumption",
|
|
1288
|
+
"MATCH:chart_legend_generation",
|
|
1289
|
+
"MATCH:infobutton_timeschedule",
|
|
1290
|
+
"MATCH:infobutton_fixedtimecycle",
|
|
1291
|
+
"MATCH:infobutton_randomtimecycle",
|
|
1292
|
+
"MATCH:infobutton_timer",
|
|
1293
|
+
"MATCH:infobutton_poweronbehavior",
|
|
1294
|
+
"MATCH:infobutton_history"
|
|
1287
1295
|
],
|
|
1288
1296
|
"replacements": {
|
|
1289
1297
|
"REGEX:% %1\\$s.*?\\)%": "{0}",
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import I18n from '@i18n'
|
|
2
|
+
import res from '@res'
|
|
3
|
+
import React, { useMemo, useState } from 'react'
|
|
4
|
+
import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
5
|
+
import { Modal, Utils } from 'tuya-panel-kit'
|
|
6
|
+
import ThemeType from '../config/themeType'
|
|
7
|
+
|
|
8
|
+
const { convertX: cx, height, statusBarHeight } = Utils.RatioUtils
|
|
9
|
+
const { withTheme } = Utils.ThemeUtils
|
|
10
|
+
|
|
11
|
+
interface FeatureInfoProps {
|
|
12
|
+
theme?: ThemeType
|
|
13
|
+
title: string
|
|
14
|
+
content: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const FeatureInfo = (props: FeatureInfoProps) => {
|
|
18
|
+
const [visible, setVisible] = useState(false)
|
|
19
|
+
|
|
20
|
+
const styles = useMemo(() => getStyles(props.theme), [props.theme])
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View>
|
|
24
|
+
<TouchableOpacity onPress={() => setVisible(true)}>
|
|
25
|
+
<Image source={{ uri: res.ic_info }} style={styles.icon}/>
|
|
26
|
+
</TouchableOpacity>
|
|
27
|
+
<Modal visible={visible} onMaskPress={() => setVisible(false)}>
|
|
28
|
+
<View>
|
|
29
|
+
<View style={styles.header}>
|
|
30
|
+
<View style={{ width: cx(40) }} />
|
|
31
|
+
<Text style={styles.title}>{props.title}</Text>
|
|
32
|
+
<TouchableOpacity onPress={() => setVisible(false)}>
|
|
33
|
+
<Text style={styles.confirm}>{I18n.getLang('home_screen_home_dialog_yes_con')}</Text>
|
|
34
|
+
</TouchableOpacity>
|
|
35
|
+
</View>
|
|
36
|
+
<ScrollView style={styles.contentContainer}>
|
|
37
|
+
<Text style={styles.content}>{props.content}</Text>
|
|
38
|
+
</ScrollView>
|
|
39
|
+
</View>
|
|
40
|
+
</Modal>
|
|
41
|
+
</View>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getStyles = (theme: ThemeType | undefined) => StyleSheet.create({
|
|
46
|
+
icon: {
|
|
47
|
+
width: cx(24),
|
|
48
|
+
height: cx(24),
|
|
49
|
+
marginHorizontal: cx(5),
|
|
50
|
+
tintColor: theme?.global.brand
|
|
51
|
+
},
|
|
52
|
+
header: {
|
|
53
|
+
backgroundColor: theme?.card.head,
|
|
54
|
+
flexDirection: 'row',
|
|
55
|
+
height: cx(60),
|
|
56
|
+
justifyContent: 'space-between',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
borderTopLeftRadius: cx(10),
|
|
59
|
+
borderTopRightRadius: cx(10),
|
|
60
|
+
paddingHorizontal: cx(8)
|
|
61
|
+
},
|
|
62
|
+
title: {
|
|
63
|
+
color: theme?.global.fontColor,
|
|
64
|
+
fontSize: cx(16),
|
|
65
|
+
fontWeight: 'bold'
|
|
66
|
+
},
|
|
67
|
+
confirm: {
|
|
68
|
+
color: theme?.button.primary,
|
|
69
|
+
fontSize: cx(16)
|
|
70
|
+
},
|
|
71
|
+
contentContainer: {
|
|
72
|
+
height: height - statusBarHeight - cx(100),
|
|
73
|
+
paddingHorizontal: cx(16),
|
|
74
|
+
backgroundColor: theme?.global.background
|
|
75
|
+
},
|
|
76
|
+
content: {
|
|
77
|
+
color: theme?.global.fontColor,
|
|
78
|
+
fontSize: cx(14)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export default withTheme(FeatureInfo)
|
package/src/components/Page.tsx
CHANGED
|
@@ -29,6 +29,7 @@ interface PageProps extends PropsWithChildren<ViewProps> {
|
|
|
29
29
|
showGreenery?: boolean
|
|
30
30
|
greeneryIcon?: string | undefined | number
|
|
31
31
|
loading?: boolean
|
|
32
|
+
info?: React.ReactNode
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const Page = (props: PageProps) => {
|
|
@@ -97,6 +98,7 @@ const Page = (props: PageProps) => {
|
|
|
97
98
|
headlineIconContent={props.headlineIconContent}
|
|
98
99
|
headlineTopContent={props.headlineTopContent}
|
|
99
100
|
headlineContent={props.headlineContent}
|
|
101
|
+
info={props.info}
|
|
100
102
|
/>}
|
|
101
103
|
{props.children}
|
|
102
104
|
</View>
|
|
@@ -17,6 +17,7 @@ interface LdvTopNameProps {
|
|
|
17
17
|
headlineIconContent?: React.ReactNode
|
|
18
18
|
headlineTopContent?: React.ReactNode
|
|
19
19
|
headlineContent?: React.ReactNode
|
|
20
|
+
info?: React.ReactNode
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const LdvTopName = (props: LdvTopNameProps) => {
|
|
@@ -28,6 +29,16 @@ const LdvTopName = (props: LdvTopNameProps) => {
|
|
|
28
29
|
marginHorizontal: cx(24),
|
|
29
30
|
marginBottom: cx(12),
|
|
30
31
|
},
|
|
32
|
+
headlineContainer: {
|
|
33
|
+
flexDirection: 'row',
|
|
34
|
+
justifyContent: 'space-between',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
},
|
|
37
|
+
headline: {
|
|
38
|
+
flexDirection: 'row',
|
|
39
|
+
flex: 1,
|
|
40
|
+
alignItems: 'center'
|
|
41
|
+
},
|
|
31
42
|
title: {
|
|
32
43
|
color: props.theme?.global.brand,
|
|
33
44
|
fontSize: cx(24),
|
|
@@ -38,12 +49,8 @@ const LdvTopName = (props: LdvTopNameProps) => {
|
|
|
38
49
|
{props.headlineTopContent && props.headlineTopContent}
|
|
39
50
|
{!props.headlineTopContent && <Spacer height={cx(38)} />}
|
|
40
51
|
<View
|
|
41
|
-
style={
|
|
42
|
-
|
|
43
|
-
justifyContent: 'space-between',
|
|
44
|
-
alignItems: 'center',
|
|
45
|
-
}}>
|
|
46
|
-
{props.headlineContent ? props.headlineContent : <View style={{ flexDirection: 'row', flex: 1 }}>
|
|
52
|
+
style={styles.headlineContainer}>
|
|
53
|
+
{props.headlineContent ? props.headlineContent : <View style={styles.headline}>
|
|
47
54
|
<Text style={styles.title}>
|
|
48
55
|
{props.title}
|
|
49
56
|
</Text>
|
|
@@ -52,6 +59,7 @@ const LdvTopName = (props: LdvTopNameProps) => {
|
|
|
52
59
|
resizeMode="contain"
|
|
53
60
|
style={{ height: cx(16), width: cx(16), left: 0 }}
|
|
54
61
|
/> || null}
|
|
62
|
+
{props.info && props.info || null}
|
|
55
63
|
</View>}
|
|
56
64
|
{props.rightIcon && <TouchableOpacity
|
|
57
65
|
accessibilityLabel={"RightIcon"}
|
package/src/hooks/Hooks.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { xLog } from '@utils'
|
|
2
|
+
import { useFocusEffect, useIsFocused, useNavigation, useRoute } from '@react-navigation/native'
|
|
2
3
|
import { Result } from 'models/modules/Result'
|
|
3
4
|
import { useCallback, useEffect, useRef } from 'react'
|
|
5
|
+
import { AppState, AppStateStatus, DeviceEventEmitter, InteractionManager, Platform } from 'react-native'
|
|
4
6
|
|
|
5
7
|
export function createParams<T>(params: T): T {
|
|
6
8
|
return { ...params }
|
|
@@ -12,7 +14,7 @@ export function useParams<T extends any>(): T {
|
|
|
12
14
|
|
|
13
15
|
export function useCurrentPage(routeName: string): boolean {
|
|
14
16
|
const navigation = useNavigation()
|
|
15
|
-
const { index, routes} = navigation.getState()
|
|
17
|
+
const { index, routes } = navigation.getState()
|
|
16
18
|
return routeName === routes[index].name
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -21,44 +23,44 @@ export function useCurrentPage(routeName: string): boolean {
|
|
|
21
23
|
* 用于在指定时间内阻止长连接响应更新界面状态
|
|
22
24
|
*/
|
|
23
25
|
export function useDpResponseValidator(timeoutMs: number = 1000) {
|
|
24
|
-
const dpTimestamps = useRef<{[dpKey: string]: number}>({})
|
|
25
|
-
|
|
26
|
+
const dpTimestamps = useRef<{ [dpKey: string]: number }>({})
|
|
27
|
+
|
|
26
28
|
// dp 响应时间校验函数
|
|
27
29
|
const sendDpWithTimestamps = useCallback((dpKey: string, sendFunc: () => Promise<any>) => {
|
|
28
30
|
// 记录发送时间戳
|
|
29
|
-
const timestamp = Date.now()
|
|
30
|
-
dpTimestamps.current[dpKey] = timestamp
|
|
31
|
+
const timestamp = Date.now()
|
|
32
|
+
dpTimestamps.current[dpKey] = timestamp
|
|
31
33
|
|
|
32
34
|
// 发送命令
|
|
33
|
-
return sendFunc()
|
|
34
|
-
}, [])
|
|
35
|
+
return sendFunc()
|
|
36
|
+
}, [])
|
|
35
37
|
|
|
36
38
|
// 监听 dp 响应,比较时间戳
|
|
37
39
|
const onDpResponse = useCallback((dpKey: string) => {
|
|
38
|
-
const sendTimestamp = dpTimestamps.current[dpKey]
|
|
40
|
+
const sendTimestamp = dpTimestamps.current[dpKey]
|
|
39
41
|
if (sendTimestamp) {
|
|
40
|
-
const responseTimestamp = Date.now()
|
|
41
|
-
const timeDiff = responseTimestamp - sendTimestamp
|
|
42
|
-
|
|
42
|
+
const responseTimestamp = Date.now()
|
|
43
|
+
const timeDiff = responseTimestamp - sendTimestamp
|
|
44
|
+
|
|
43
45
|
if (timeDiff <= timeoutMs) {
|
|
44
46
|
// 指定时间内收到响应,不接收更新
|
|
45
|
-
return true
|
|
47
|
+
return true // 阻止更新
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
|
-
return false
|
|
49
|
-
}, [timeoutMs])
|
|
50
|
+
return false // 允许更新
|
|
51
|
+
}, [timeoutMs])
|
|
50
52
|
|
|
51
53
|
// 组件卸载时清理所有记录
|
|
52
54
|
useEffect(() => {
|
|
53
55
|
return () => {
|
|
54
|
-
dpTimestamps.current = {}
|
|
55
|
-
}
|
|
56
|
-
}, [])
|
|
56
|
+
dpTimestamps.current = {}
|
|
57
|
+
}
|
|
58
|
+
}, [])
|
|
57
59
|
|
|
58
60
|
return {
|
|
59
61
|
sendDpWithTimestamps,
|
|
60
62
|
onDpResponse,
|
|
61
|
-
}
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
interface IControlData {
|
|
@@ -82,7 +84,7 @@ interface IControlDataOptions {
|
|
|
82
84
|
* 通用控制支持device和group
|
|
83
85
|
* @param control Tuya Control Formatter
|
|
84
86
|
* @param sendValueFunc send tuya control function
|
|
85
|
-
* @returns
|
|
87
|
+
* @returns
|
|
86
88
|
*/
|
|
87
89
|
export function useControlData(control: any, sendValueFunc: (value: any) => Promise<Result<any>>): (
|
|
88
90
|
controlValue: IControlData,
|
|
@@ -104,10 +106,71 @@ export function useControlData(control: any, sendValueFunc: (value: any) => Prom
|
|
|
104
106
|
value: controlValue.colorMode ? valueConvert(controlValue.value) : 0,
|
|
105
107
|
temperature: !controlValue.colorMode ? temperatureConvert(controlValue.temperature) : 0,
|
|
106
108
|
brightness: !controlValue.colorMode ? brightnessConvert(controlValue.brightness) : 0,
|
|
107
|
-
})
|
|
109
|
+
})
|
|
108
110
|
return await sendValueFunc(value)
|
|
109
111
|
},
|
|
110
112
|
[]
|
|
111
|
-
)
|
|
113
|
+
)
|
|
112
114
|
return setControlData
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 这是一个增强版的 FocusEffect
|
|
119
|
+
* 1. 使用 useCallback 解决重复调用问题
|
|
120
|
+
* 2. [Android] 监听 AppState 解决从原生 Activity 返回不触发的问题
|
|
121
|
+
* 3. [iOS] 监听原生 ViewControllerWillAppear 事件解决从原生 VC 返回不 new的问题
|
|
122
|
+
*/
|
|
123
|
+
export const useSmartFocusEffect = (
|
|
124
|
+
effect: () => void | (() => void),
|
|
125
|
+
deps: any[] = []
|
|
126
|
+
) => {
|
|
127
|
+
const isFocused = useIsFocused()
|
|
128
|
+
const appState = useRef<AppStateStatus>(AppState.currentState)
|
|
129
|
+
const stableEffect = useCallback(effect, deps)
|
|
130
|
+
// 1. react-navigation 内部的焦点处理
|
|
131
|
+
useFocusEffect(
|
|
132
|
+
useCallback(() => {
|
|
133
|
+
const task = InteractionManager.runAfterInteractions(() => {
|
|
134
|
+
stableEffect()
|
|
135
|
+
})
|
|
136
|
+
return () => task.cancel()
|
|
137
|
+
}, [stableEffect])
|
|
138
|
+
)
|
|
139
|
+
// 2. 平台特定的返回事件处理
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
// --- Android 平台方案 ---
|
|
142
|
+
if (Platform.OS === 'android') {
|
|
143
|
+
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
|
144
|
+
if (
|
|
145
|
+
appState.current.match(/inactive|background/) &&
|
|
146
|
+
nextAppState === 'active' &&
|
|
147
|
+
isFocused
|
|
148
|
+
) {
|
|
149
|
+
xLog('[SmartFocus] App returned from Native/Background on Android')
|
|
150
|
+
stableEffect()
|
|
151
|
+
}
|
|
152
|
+
appState.current = nextAppState
|
|
153
|
+
}
|
|
154
|
+
AppState.addEventListener('change', handleAppStateChange)
|
|
155
|
+
return () => {
|
|
156
|
+
(AppState as any).removeEventListener('change', handleAppStateChange)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// --- iOS 平台方案 ---
|
|
160
|
+
if (Platform.OS === 'ios') {
|
|
161
|
+
const subscription = DeviceEventEmitter.addListener(
|
|
162
|
+
'RNViewControllerWillAppear',
|
|
163
|
+
() => {
|
|
164
|
+
// 同样需要检查 isFocused,确保只有栈顶的 RN 页面响应
|
|
165
|
+
if (isFocused) {
|
|
166
|
+
xLog('[SmartFocus] iOS ViewController will appear')
|
|
167
|
+
stableEffect()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
return () => {
|
|
172
|
+
subscription.remove()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}, [isFocused, stableEffect])
|
|
113
176
|
}
|