@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 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
@@ -4,7 +4,7 @@
4
4
  "name": "@ledvance/base",
5
5
  "pid": [],
6
6
  "uiid": "",
7
- "version": "1.3.100",
7
+ "version": "1.3.101",
8
8
  "scripts": {
9
9
  "prepublishOnly": "python update-localazy.py"
10
10
  },
@@ -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)
@@ -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
- flexDirection: 'row',
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"}
@@ -1,6 +1,8 @@
1
- import { useRoute, useNavigation } from '@react-navigation/core'
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
  }