@momo-kits/foundation 0.169.4-beta.8 → 0.169.4
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/Application/BottomSheet.tsx +5 -11
- package/Application/ModalScreen.tsx +1 -7
- package/Application/NavigationContainer.tsx +0 -2
- package/Application/StackScreen.tsx +1 -4
- package/Application/__tests__/modalLayout.test.ts +3 -3
- package/Application/modalLayout.ts +2 -2
- package/Popup/PopupNotify.tsx +5 -9
- package/package.json +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -145
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/constants.ts +0 -104
- package/Application/KitAdoption/index.ts +0 -5
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -48
- package/Application/KitAdoption/utils.ts +0 -393
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -235
- package/Application/__tests__/KitAdoption.test.ts +0 -260
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
|
@@ -18,7 +18,6 @@ import Animated, {
|
|
|
18
18
|
} from 'react-native-reanimated';
|
|
19
19
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
20
20
|
import { DUO_ORIENTATIONS, styles } from './BottomSheet.styles';
|
|
21
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
22
21
|
import {
|
|
23
22
|
useBottomSheetLayout,
|
|
24
23
|
useDuoKeyboardLift,
|
|
@@ -174,16 +173,11 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
174
173
|
);
|
|
175
174
|
|
|
176
175
|
const screen = (
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{...props}
|
|
183
|
-
{...props.route.params}
|
|
184
|
-
onRequestClose={onRequestClose}
|
|
185
|
-
/>
|
|
186
|
-
</AdoptionProbe>
|
|
176
|
+
<Screen
|
|
177
|
+
{...props}
|
|
178
|
+
{...props.route.params}
|
|
179
|
+
onRequestClose={onRequestClose}
|
|
180
|
+
/>
|
|
187
181
|
);
|
|
188
182
|
|
|
189
183
|
return (
|
|
@@ -24,7 +24,6 @@ import Navigation from './Navigation';
|
|
|
24
24
|
import { ModalParams } from './types';
|
|
25
25
|
import BottomSheet from './BottomSheet';
|
|
26
26
|
import { isIPhoneDuo } from './utils';
|
|
27
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
28
27
|
import { useModalLayout } from './modalLayout';
|
|
29
28
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
30
29
|
|
|
@@ -183,12 +182,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
183
182
|
modalStyle,
|
|
184
183
|
]}
|
|
185
184
|
>
|
|
186
|
-
<
|
|
187
|
-
screenName={Component?.name || Component?.type?.name || 'Invalid'}
|
|
188
|
-
navigation={props.navigation}
|
|
189
|
-
>
|
|
190
|
-
<Component {...params} onRequestClose={onDismiss} />
|
|
191
|
-
</AdoptionProbe>
|
|
185
|
+
<Component {...params} onRequestClose={onDismiss} />
|
|
192
186
|
</Animated.View>
|
|
193
187
|
</KeyboardAvoidingView>
|
|
194
188
|
</Container>
|
|
@@ -30,7 +30,6 @@ import { HeaderBackground } from './Components/HeaderBackground';
|
|
|
30
30
|
import { useScreenLayout } from '../Layout';
|
|
31
31
|
import { getHeaderRowOptions } from '../Layout/utils';
|
|
32
32
|
import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
|
|
33
|
-
import { AdoptionBadge } from './KitAdoption';
|
|
34
33
|
|
|
35
34
|
const Stack = createStackNavigator();
|
|
36
35
|
|
|
@@ -87,7 +86,6 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
87
86
|
initialParams={initialParams}
|
|
88
87
|
localize={localize}
|
|
89
88
|
/>
|
|
90
|
-
<AdoptionBadge />
|
|
91
89
|
</MiniAppContext.Provider>
|
|
92
90
|
</ReservedRegionsProvider>
|
|
93
91
|
</SafeAreaProvider>
|
|
@@ -8,7 +8,6 @@ import { SwipeBackHaptic } from './Components/SwipeBackHaptic';
|
|
|
8
8
|
import { useAppState } from './hooks';
|
|
9
9
|
import { createScreenTracking } from './StackScreen.tracking';
|
|
10
10
|
import { TooltipPortalHost, TooltipPortalProvider } from './TooltipPortal';
|
|
11
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
12
11
|
|
|
13
12
|
const runAfterInteractions = InteractionManager.runAfterInteractions;
|
|
14
13
|
|
|
@@ -284,9 +283,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
284
283
|
}}
|
|
285
284
|
>
|
|
286
285
|
<TooltipPortalProvider>
|
|
287
|
-
<
|
|
288
|
-
<Component heightHeader={heightHeader} {...data} />
|
|
289
|
-
</AdoptionProbe>
|
|
286
|
+
<Component heightHeader={heightHeader} {...data} />
|
|
290
287
|
<TooltipPortalHost />
|
|
291
288
|
<SwipeBackHaptic />
|
|
292
289
|
</TooltipPortalProvider>
|
|
@@ -16,10 +16,10 @@ describe('getModalLayout', () => {
|
|
|
16
16
|
expect(layout.isFoldable).toBe(false);
|
|
17
17
|
expect(layout.popup.width).toBe(342);
|
|
18
18
|
expect(layout.popup.maxHeight).toBeUndefined();
|
|
19
|
-
expect(layout.popup.radius).toBe(
|
|
19
|
+
expect(layout.popup.radius).toBe(24);
|
|
20
20
|
expect(layout.sheet.width).toBeUndefined();
|
|
21
21
|
expect(layout.sheet.maxHeight).toBeUndefined();
|
|
22
|
-
expect(layout.sheet.radius).toBe(
|
|
22
|
+
expect(layout.sheet.radius).toBe(24);
|
|
23
23
|
expect(layout.scrimOpacity).toBe(0.6);
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -33,7 +33,7 @@ describe('getModalLayout', () => {
|
|
|
33
33
|
expect(layout.isFoldable).toBe(false);
|
|
34
34
|
expect(layout.popup.width).toBe(844 - 48);
|
|
35
35
|
expect(layout.popup.maxHeight).toBeUndefined();
|
|
36
|
-
expect(layout.popup.radius).toBe(
|
|
36
|
+
expect(layout.popup.radius).toBe(24);
|
|
37
37
|
expect(layout.sheet.width).toBeUndefined();
|
|
38
38
|
expect(layout.scrimOpacity).toBe(0.6);
|
|
39
39
|
});
|
|
@@ -112,9 +112,9 @@ const getModalLayout = ({
|
|
|
112
112
|
minWidth: popupWidth,
|
|
113
113
|
maxWidth: popupWidth,
|
|
114
114
|
margin: Spacing.XL,
|
|
115
|
-
radius: Radius.
|
|
115
|
+
radius: Radius.XL,
|
|
116
116
|
},
|
|
117
|
-
sheet: { margin: 0, radius: Radius.
|
|
117
|
+
sheet: { margin: 0, radius: Radius.XL },
|
|
118
118
|
scrimOpacity: SCRIM_OPACITY,
|
|
119
119
|
};
|
|
120
120
|
}
|
package/Popup/PopupNotify.tsx
CHANGED
|
@@ -183,18 +183,12 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
183
183
|
const body = (
|
|
184
184
|
<>
|
|
185
185
|
{!!image && (
|
|
186
|
-
<Image
|
|
187
|
-
source={{ uri: image }}
|
|
188
|
-
style={[
|
|
189
|
-
styles.image,
|
|
190
|
-
{ borderTopLeftRadius: radius, borderTopRightRadius: radius },
|
|
191
|
-
]}
|
|
192
|
-
/>
|
|
186
|
+
<Image source={{ uri: image }} style={styles.image} />
|
|
193
187
|
)}
|
|
194
188
|
<View style={[styles.content, isFoldable && styles.shrink]}>
|
|
195
189
|
<Text
|
|
196
190
|
typography={'header_default_bold'}
|
|
197
|
-
numberOfLines={
|
|
191
|
+
numberOfLines={2}
|
|
198
192
|
accessibilityLabel={'title_popup_notify'}
|
|
199
193
|
// keeps the title clear of the close button on a text only popup
|
|
200
194
|
style={
|
|
@@ -250,6 +244,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
250
244
|
width: popupWidth,
|
|
251
245
|
maxHeight,
|
|
252
246
|
},
|
|
247
|
+
styles.card,
|
|
253
248
|
showBaseLineDebug && styles.debugBaseLine,
|
|
254
249
|
]}
|
|
255
250
|
>
|
|
@@ -262,9 +257,10 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
262
257
|
|
|
263
258
|
const styles = StyleSheet.create({
|
|
264
259
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
260
|
+
// clips the image and content to the card radius
|
|
261
|
+
card: { overflow: 'hidden' },
|
|
265
262
|
image: {
|
|
266
263
|
aspectRatio: 1.7777,
|
|
267
|
-
overflow: 'hidden',
|
|
268
264
|
},
|
|
269
265
|
shrink: { flexShrink: 1 },
|
|
270
266
|
content: { padding: Spacing.XL },
|
package/package.json
CHANGED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import React, { useState, useSyncExternalStore } from 'react';
|
|
2
|
-
import { Share, TouchableOpacity, View } from 'react-native';
|
|
3
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
-
|
|
5
|
-
import { Colors, Spacing } from '../../Consts';
|
|
6
|
-
import { Text } from '../../Text';
|
|
7
|
-
import { adoptionStore } from './AdoptionStore';
|
|
8
|
-
import { styles } from './styles';
|
|
9
|
-
import type { ScreenSnapshot } from './types';
|
|
10
|
-
|
|
11
|
-
/** Subscribes a React listener to the adoption store. Stable identity for useSyncExternalStore. */
|
|
12
|
-
const subscribeToStore = (listener: () => void): (() => void) =>
|
|
13
|
-
adoptionStore.subscribe(listener);
|
|
14
|
-
|
|
15
|
-
const getLatestSnapshot = (): ScreenSnapshot | undefined =>
|
|
16
|
-
adoptionStore.getLatest();
|
|
17
|
-
|
|
18
|
-
const getScreenCount = (): number => adoptionStore.getAll().length;
|
|
19
|
-
|
|
20
|
-
/** Share of classified nodes that are app-authored kit: K / (K + R + O). */
|
|
21
|
-
const getAdoptionRate = (snapshot: ScreenSnapshot): number => {
|
|
22
|
-
const { K, R, O } = snapshot.counts;
|
|
23
|
-
const total = K + R + O;
|
|
24
|
-
return total === 0 ? 0 : K / total;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const formatSummary = (snapshot: ScreenSnapshot | undefined): string => {
|
|
28
|
-
if (!snapshot) return 'Kit adoption: chưa có dữ liệu';
|
|
29
|
-
const { K, R, O } = snapshot.counts;
|
|
30
|
-
const percent = Math.round(getAdoptionRate(snapshot) * 100);
|
|
31
|
-
return `Kit ${percent}% · K${K} R${R} O${O}`;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const handleRemeasure = (): void => {
|
|
35
|
-
adoptionStore.remeasureActive();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const handleShare = (): void => {
|
|
39
|
-
Share.share({ message: JSON.stringify(adoptionStore.getAll()) }).catch(
|
|
40
|
-
() => undefined,
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const handleClear = (): void => {
|
|
45
|
-
adoptionStore.clear();
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Dev-only floating badge showing the latest screen's kit adoption. Tap to
|
|
50
|
-
* expand actions (re-measure, share JSON, clear). Renders nothing in release builds.
|
|
51
|
-
*/
|
|
52
|
-
const AdoptionBadge: React.FC = () => {
|
|
53
|
-
const insets = useSafeAreaInsets();
|
|
54
|
-
const [isExpanded, setExpanded] = useState(false);
|
|
55
|
-
const latest = useSyncExternalStore(subscribeToStore, getLatestSnapshot);
|
|
56
|
-
const screenCount = useSyncExternalStore(subscribeToStore, getScreenCount);
|
|
57
|
-
|
|
58
|
-
if (!__DEV__) return null;
|
|
59
|
-
|
|
60
|
-
const handleToggle = (): void => setExpanded(prev => !prev);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<View
|
|
64
|
-
pointerEvents="box-none"
|
|
65
|
-
style={[styles.wrapper, { top: insets.top + Spacing.S }]}
|
|
66
|
-
>
|
|
67
|
-
<TouchableOpacity
|
|
68
|
-
accessibilityRole="button"
|
|
69
|
-
onPress={handleToggle}
|
|
70
|
-
style={styles.badge}
|
|
71
|
-
>
|
|
72
|
-
<Text typography="label_xs_medium" color={Colors.black_01}>
|
|
73
|
-
{formatSummary(latest)}
|
|
74
|
-
</Text>
|
|
75
|
-
{latest ? (
|
|
76
|
-
<Text typography="description_xs_regular" color={Colors.black_01}>
|
|
77
|
-
{latest.screen_name}
|
|
78
|
-
</Text>
|
|
79
|
-
) : null}
|
|
80
|
-
</TouchableOpacity>
|
|
81
|
-
{isExpanded ? (
|
|
82
|
-
<View style={styles.panel}>
|
|
83
|
-
<Text typography="label_xs_medium" color={Colors.black_17}>
|
|
84
|
-
{`Đã đo ${screenCount} màn hình`}
|
|
85
|
-
</Text>
|
|
86
|
-
<TouchableOpacity
|
|
87
|
-
accessibilityRole="button"
|
|
88
|
-
onPress={handleRemeasure}
|
|
89
|
-
style={styles.action}
|
|
90
|
-
>
|
|
91
|
-
<Text typography="label_xs_medium" color={Colors.blue_03}>
|
|
92
|
-
Đo lại
|
|
93
|
-
</Text>
|
|
94
|
-
</TouchableOpacity>
|
|
95
|
-
<TouchableOpacity
|
|
96
|
-
accessibilityRole="button"
|
|
97
|
-
onPress={handleShare}
|
|
98
|
-
style={styles.action}
|
|
99
|
-
>
|
|
100
|
-
<Text typography="label_xs_medium" color={Colors.blue_03}>
|
|
101
|
-
Share JSON
|
|
102
|
-
</Text>
|
|
103
|
-
</TouchableOpacity>
|
|
104
|
-
<TouchableOpacity
|
|
105
|
-
accessibilityRole="button"
|
|
106
|
-
onPress={handleClear}
|
|
107
|
-
style={styles.actionOutline}
|
|
108
|
-
>
|
|
109
|
-
<Text typography="label_xs_medium" color={Colors.black_17}>
|
|
110
|
-
Xoá
|
|
111
|
-
</Text>
|
|
112
|
-
</TouchableOpacity>
|
|
113
|
-
</View>
|
|
114
|
-
) : null}
|
|
115
|
-
</View>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export { AdoptionBadge };
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { InteractionManager, Platform } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import { MiniAppContext } from '../../Context';
|
|
5
|
-
import { adoptionStore } from './AdoptionStore';
|
|
6
|
-
import { SETTLE_MS } from './constants';
|
|
7
|
-
import type {
|
|
8
|
-
FiberLike,
|
|
9
|
-
KitRegistries,
|
|
10
|
-
ScreenSnapshot,
|
|
11
|
-
WalkResult,
|
|
12
|
-
} from './types';
|
|
13
|
-
import {
|
|
14
|
-
createRegistries,
|
|
15
|
-
getAppId,
|
|
16
|
-
getCurrentFiber,
|
|
17
|
-
walkScreen,
|
|
18
|
-
} from './utils';
|
|
19
|
-
|
|
20
|
-
type NavigationLike = {
|
|
21
|
-
addListener?: (event: string, callback: () => void) => () => void;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type ProbeProps = {
|
|
25
|
-
screenName: string;
|
|
26
|
-
navigation?: NavigationLike;
|
|
27
|
-
children: React.ReactNode;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let registriesCache: KitRegistries | undefined;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Kit and RN identity sets, built on first use. Lazy require breaks the cycle
|
|
34
|
-
* between the foundation index and the Application screens that render this probe.
|
|
35
|
-
*/
|
|
36
|
-
const getRegistries = (): KitRegistries => {
|
|
37
|
-
if (!registriesCache) {
|
|
38
|
-
const kitModule = require('../../index') as Record<string, unknown>;
|
|
39
|
-
const rnModule = require('react-native') as Record<string, unknown>;
|
|
40
|
-
registriesCache = createRegistries([kitModule], rnModule);
|
|
41
|
-
}
|
|
42
|
-
return registriesCache;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/** Foundation version from its package.json, or undefined if it cannot be read. */
|
|
46
|
-
const readFoundationVersion = (): string | undefined => {
|
|
47
|
-
try {
|
|
48
|
-
const pkg = require('../../package.json') as { version?: string };
|
|
49
|
-
return pkg.version;
|
|
50
|
-
} catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const createErrorResult = (): WalkResult => ({
|
|
56
|
-
counts: { K: 0, R: 0, O: 0 },
|
|
57
|
-
kit: {},
|
|
58
|
-
rn: {},
|
|
59
|
-
other: {},
|
|
60
|
-
top_transparent: [],
|
|
61
|
-
fibers: 0,
|
|
62
|
-
truncated: false,
|
|
63
|
-
status: 'error',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/** Stops the walk at nested probes so each screen is measured on its own. */
|
|
67
|
-
const isProbeFiber = (fiber: FiberLike): boolean =>
|
|
68
|
-
fiber.type === AdoptionProbeDev;
|
|
69
|
-
|
|
70
|
-
/** Passes children through with no measurement. Used in release builds. */
|
|
71
|
-
const PassThrough = (props: ProbeProps): React.JSX.Element => (
|
|
72
|
-
<>{props.children}</>
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Dev-only wrapper around a screen. Measures the screen's fiber tree after
|
|
77
|
-
* the first interactions settle, and again on focus. Release builds use PassThrough.
|
|
78
|
-
*/
|
|
79
|
-
class AdoptionProbeDev extends React.Component<ProbeProps> {
|
|
80
|
-
static contextType = MiniAppContext;
|
|
81
|
-
|
|
82
|
-
private timer: ReturnType<typeof setTimeout> | undefined;
|
|
83
|
-
private interaction: { cancel: () => void } | undefined;
|
|
84
|
-
private unsubscribeFocus: (() => void) | undefined;
|
|
85
|
-
|
|
86
|
-
componentDidMount(): void {
|
|
87
|
-
adoptionStore.setActive(this.measure);
|
|
88
|
-
this.unsubscribeFocus = this.props.navigation?.addListener?.(
|
|
89
|
-
'focus',
|
|
90
|
-
this.handleFocus,
|
|
91
|
-
);
|
|
92
|
-
this.interaction = InteractionManager.runAfterInteractions(() => {
|
|
93
|
-
this.timer = setTimeout(this.measure, SETTLE_MS);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
componentWillUnmount(): void {
|
|
98
|
-
if (this.timer !== undefined) clearTimeout(this.timer);
|
|
99
|
-
this.interaction?.cancel();
|
|
100
|
-
this.unsubscribeFocus?.();
|
|
101
|
-
adoptionStore.clearActive(this.measure);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
render(): React.ReactNode {
|
|
105
|
-
return this.props.children;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private handleFocus = (): void => {
|
|
109
|
-
adoptionStore.setActive(this.measure);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
private measure = (): void => {
|
|
113
|
-
const startedAt = Date.now();
|
|
114
|
-
let result: WalkResult;
|
|
115
|
-
try {
|
|
116
|
-
const self = getCurrentFiber(
|
|
117
|
-
(this as unknown as { _reactInternals: FiberLike })._reactInternals,
|
|
118
|
-
);
|
|
119
|
-
const root = self.child;
|
|
120
|
-
if (!root) return;
|
|
121
|
-
result = walkScreen(root, getRegistries(), isProbeFiber);
|
|
122
|
-
} catch {
|
|
123
|
-
result = createErrorResult();
|
|
124
|
-
}
|
|
125
|
-
this.save(result, startedAt);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
private save(result: WalkResult, startedAt: number): void {
|
|
129
|
-
const snapshot: ScreenSnapshot = {
|
|
130
|
-
...result,
|
|
131
|
-
app_id: getAppId(this.context),
|
|
132
|
-
screen_name: this.props.screenName,
|
|
133
|
-
foundation_version: readFoundationVersion(),
|
|
134
|
-
platform: Platform.OS,
|
|
135
|
-
ts: Date.now(),
|
|
136
|
-
walk_ms: Date.now() - startedAt,
|
|
137
|
-
};
|
|
138
|
-
adoptionStore.save(snapshot);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const AdoptionProbe = __DEV__ ? AdoptionProbeDev : PassThrough;
|
|
143
|
-
|
|
144
|
-
export type { NavigationLike, ProbeProps };
|
|
145
|
-
export { AdoptionProbe, AdoptionProbeDev };
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { ScreenSnapshot } from './types';
|
|
2
|
-
|
|
3
|
-
type Listener = () => void;
|
|
4
|
-
type Measure = () => void;
|
|
5
|
-
|
|
6
|
-
/** Total classified nodes in a snapshot, used to keep the richer measurement. */
|
|
7
|
-
const getTotal = (snapshot: ScreenSnapshot): number =>
|
|
8
|
-
snapshot.counts.K + snapshot.counts.R + snapshot.counts.O;
|
|
9
|
-
|
|
10
|
-
const getKey = (snapshot: ScreenSnapshot): string =>
|
|
11
|
-
`${snapshot.app_id}/${snapshot.screen_name}`;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* In-memory, dev-only store of per-screen snapshots. Keeps one snapshot per
|
|
15
|
-
* app and screen (the one with the most nodes) and holds the active measurer.
|
|
16
|
-
*/
|
|
17
|
-
class AdoptionStore {
|
|
18
|
-
private snapshots = new Map<string, ScreenSnapshot>();
|
|
19
|
-
private listeners = new Set<Listener>();
|
|
20
|
-
private latestKey: string | undefined;
|
|
21
|
-
private activeMeasure: Measure | undefined;
|
|
22
|
-
|
|
23
|
-
/** Stores a snapshot unless the stored one for the same screen is larger. */
|
|
24
|
-
save(snapshot: ScreenSnapshot): void {
|
|
25
|
-
const key = getKey(snapshot);
|
|
26
|
-
const existing = this.snapshots.get(key);
|
|
27
|
-
if (!existing || getTotal(existing) <= getTotal(snapshot)) {
|
|
28
|
-
this.snapshots.set(key, snapshot);
|
|
29
|
-
}
|
|
30
|
-
this.latestKey = key;
|
|
31
|
-
this.notify();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
getAll(): ScreenSnapshot[] {
|
|
35
|
-
return Array.from(this.snapshots.values());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** The screen saved most recently, or undefined when nothing is saved. */
|
|
39
|
-
getLatest(): ScreenSnapshot | undefined {
|
|
40
|
-
return this.latestKey === undefined
|
|
41
|
-
? undefined
|
|
42
|
-
: this.snapshots.get(this.latestKey);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
clear(): void {
|
|
46
|
-
this.snapshots.clear();
|
|
47
|
-
this.latestKey = undefined;
|
|
48
|
-
this.notify();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Registers a change listener and returns its unsubscribe function. */
|
|
52
|
-
subscribe(listener: Listener): () => void {
|
|
53
|
-
this.listeners.add(listener);
|
|
54
|
-
return () => {
|
|
55
|
-
this.listeners.delete(listener);
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
setActive(measure: Measure): void {
|
|
60
|
-
this.activeMeasure = measure;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Clears the active measurer only if it is still the given one. */
|
|
64
|
-
clearActive(measure: Measure): void {
|
|
65
|
-
if (this.activeMeasure === measure) this.activeMeasure = undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
remeasureActive(): void {
|
|
69
|
-
this.activeMeasure?.();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private notify(): void {
|
|
73
|
-
this.listeners.forEach(listener => listener());
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const adoptionStore = new AdoptionStore();
|
|
78
|
-
|
|
79
|
-
export type { Listener, Measure };
|
|
80
|
-
export { AdoptionStore, adoptionStore };
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
const SETTLE_MS = 3000;
|
|
2
|
-
const MAX_FIBERS = 20000;
|
|
3
|
-
const SLOT_SCAN_DEPTH = 4;
|
|
4
|
-
const TOP_TRANSPARENT_LIMIT = 10;
|
|
5
|
-
|
|
6
|
-
// Generic lib names (Badge, Title, Logo, ...) are left out on purpose so app components with the same name are not counted as kit.
|
|
7
|
-
const LIB_UNIT_NAMES: string[] = [
|
|
8
|
-
'AutoComplete',
|
|
9
|
-
'Avatar',
|
|
10
|
-
'Calendar',
|
|
11
|
-
'Carousel',
|
|
12
|
-
'Chip',
|
|
13
|
-
'Collapse',
|
|
14
|
-
'DateTimePicker',
|
|
15
|
-
'ProgressInfo',
|
|
16
|
-
'Rating',
|
|
17
|
-
'Slider',
|
|
18
|
-
'Stepper',
|
|
19
|
-
'Steps',
|
|
20
|
-
'SuggestAction',
|
|
21
|
-
'Swipe',
|
|
22
|
-
'TabView',
|
|
23
|
-
'TabBar',
|
|
24
|
-
'CardTabBar',
|
|
25
|
-
'ScrollableTabBar',
|
|
26
|
-
'HeaderSliderBanner',
|
|
27
|
-
'TrustBanner',
|
|
28
|
-
'Uploader',
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const RN_PRIMITIVE_NAMES: string[] = [
|
|
32
|
-
'View',
|
|
33
|
-
'Text',
|
|
34
|
-
'Image',
|
|
35
|
-
'ImageBackground',
|
|
36
|
-
'TextInput',
|
|
37
|
-
'ScrollView',
|
|
38
|
-
'FlatList',
|
|
39
|
-
'SectionList',
|
|
40
|
-
'VirtualizedList',
|
|
41
|
-
'TouchableOpacity',
|
|
42
|
-
'TouchableHighlight',
|
|
43
|
-
'TouchableWithoutFeedback',
|
|
44
|
-
'Pressable',
|
|
45
|
-
'Switch',
|
|
46
|
-
'ActivityIndicator',
|
|
47
|
-
'Modal',
|
|
48
|
-
'KeyboardAvoidingView',
|
|
49
|
-
'RefreshControl',
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
/** Foundation exports that come from third-party or RN and must not count as kit. */
|
|
53
|
-
const FOUNDATION_REEXPORT_NAMES: string[] = [
|
|
54
|
-
'TouchableOpacity',
|
|
55
|
-
'LinearGradient',
|
|
56
|
-
'SafeAreaView',
|
|
57
|
-
'SafeAreaProvider',
|
|
58
|
-
'SafeAreaConsumer',
|
|
59
|
-
'SafeAreaInsetsContext',
|
|
60
|
-
'SafeAreaFrameContext',
|
|
61
|
-
'withSafeAreaInsets',
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
const FIBER_TAG = {
|
|
65
|
-
FunctionComponent: 0,
|
|
66
|
-
ClassComponent: 1,
|
|
67
|
-
HostRoot: 3,
|
|
68
|
-
HostComponent: 5,
|
|
69
|
-
HostText: 6,
|
|
70
|
-
Fragment: 7,
|
|
71
|
-
ContextProvider: 10,
|
|
72
|
-
ForwardRef: 11,
|
|
73
|
-
MemoComponent: 14,
|
|
74
|
-
SimpleMemoComponent: 15,
|
|
75
|
-
} as const;
|
|
76
|
-
|
|
77
|
-
/** Fiber tags that are component fibers; every other tag is walked through without counting. */
|
|
78
|
-
const COUNTED_FIBER_TAGS: number[] = [
|
|
79
|
-
FIBER_TAG.FunctionComponent,
|
|
80
|
-
FIBER_TAG.ClassComponent,
|
|
81
|
-
FIBER_TAG.HostComponent,
|
|
82
|
-
FIBER_TAG.ForwardRef,
|
|
83
|
-
FIBER_TAG.SimpleMemoComponent,
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const HOST_RN_PREFIXES: string[] = ['RCT', 'Android'];
|
|
87
|
-
|
|
88
|
-
const REACT_MEMO_TYPE = Symbol.for('react.memo');
|
|
89
|
-
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
|
|
90
|
-
|
|
91
|
-
export {
|
|
92
|
-
COUNTED_FIBER_TAGS,
|
|
93
|
-
FIBER_TAG,
|
|
94
|
-
FOUNDATION_REEXPORT_NAMES,
|
|
95
|
-
HOST_RN_PREFIXES,
|
|
96
|
-
LIB_UNIT_NAMES,
|
|
97
|
-
MAX_FIBERS,
|
|
98
|
-
REACT_FORWARD_REF_TYPE,
|
|
99
|
-
REACT_MEMO_TYPE,
|
|
100
|
-
RN_PRIMITIVE_NAMES,
|
|
101
|
-
SETTLE_MS,
|
|
102
|
-
SLOT_SCAN_DEPTH,
|
|
103
|
-
TOP_TRANSPARENT_LIMIT,
|
|
104
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { StyleSheet } from 'react-native';
|
|
2
|
-
|
|
3
|
-
import { Colors, Radius, Spacing } from '../../Consts';
|
|
4
|
-
|
|
5
|
-
// Keeps the summary pill from stretching across a narrow screen.
|
|
6
|
-
const BADGE_MAX_WIDTH = 240;
|
|
7
|
-
// Wide enough for the three action labels without clipping.
|
|
8
|
-
const PANEL_MIN_WIDTH = 180;
|
|
9
|
-
// Floats the dev overlay above screen content.
|
|
10
|
-
const BADGE_Z_INDEX = 9999;
|
|
11
|
-
|
|
12
|
-
const styles = StyleSheet.create({
|
|
13
|
-
wrapper: {
|
|
14
|
-
position: 'absolute',
|
|
15
|
-
left: 0,
|
|
16
|
-
right: 0,
|
|
17
|
-
alignItems: 'flex-end',
|
|
18
|
-
paddingRight: Spacing.L,
|
|
19
|
-
zIndex: BADGE_Z_INDEX,
|
|
20
|
-
},
|
|
21
|
-
badge: {
|
|
22
|
-
maxWidth: BADGE_MAX_WIDTH,
|
|
23
|
-
backgroundColor: Colors.black_17,
|
|
24
|
-
borderRadius: Radius.S,
|
|
25
|
-
paddingHorizontal: Spacing.S,
|
|
26
|
-
paddingVertical: Spacing.XS,
|
|
27
|
-
},
|
|
28
|
-
panel: {
|
|
29
|
-
marginTop: Spacing.XS,
|
|
30
|
-
minWidth: PANEL_MIN_WIDTH,
|
|
31
|
-
gap: Spacing.XS,
|
|
32
|
-
padding: Spacing.S,
|
|
33
|
-
backgroundColor: Colors.black_01,
|
|
34
|
-
borderRadius: Radius.M,
|
|
35
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
36
|
-
borderColor: Colors.black_05,
|
|
37
|
-
},
|
|
38
|
-
action: {
|
|
39
|
-
alignItems: 'center',
|
|
40
|
-
paddingHorizontal: Spacing.S,
|
|
41
|
-
paddingVertical: Spacing.XS,
|
|
42
|
-
borderRadius: Radius.S,
|
|
43
|
-
backgroundColor: Colors.blue_09,
|
|
44
|
-
},
|
|
45
|
-
actionOutline: {
|
|
46
|
-
alignItems: 'center',
|
|
47
|
-
paddingHorizontal: Spacing.S,
|
|
48
|
-
paddingVertical: Spacing.XS,
|
|
49
|
-
borderRadius: Radius.S,
|
|
50
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
51
|
-
borderColor: Colors.black_07,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export { styles };
|