react-native-debug-toolkit 3.3.4 → 3.5.0
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/README.md +48 -46
- package/README.zh-CN.md +48 -46
- package/android/src/main/java/com/reactnativedebugtoolkit/DebugToolkitDevConnectModule.java +0 -187
- package/bin/debug-toolkit.js +0 -16
- package/ios/DebugToolkitDevConnect.h +0 -12
- package/ios/DebugToolkitDevConnect.mm +0 -321
- package/lib/commonjs/constants/logLevels.js +19 -0
- package/lib/commonjs/constants/logLevels.js.map +1 -0
- package/lib/commonjs/core/initialize.js +36 -18
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/features/console/ConsoleLogTab.js +4 -15
- package/lib/commonjs/features/console/ConsoleLogTab.js.map +1 -1
- package/lib/commonjs/features/console/index.js +15 -8
- package/lib/commonjs/features/console/index.js.map +1 -1
- package/lib/commonjs/features/devConnect/DevConnectTab.js +18 -470
- package/lib/commonjs/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/commonjs/features/devConnect/devConnectPreferences.js +0 -12
- package/lib/commonjs/features/devConnect/devConnectPreferences.js.map +1 -1
- package/lib/commonjs/features/devConnect/devConnectUtils.js +2 -57
- package/lib/commonjs/features/devConnect/devConnectUtils.js.map +1 -1
- package/lib/commonjs/features/devConnect/index.js +1 -23
- package/lib/commonjs/features/devConnect/index.js.map +1 -1
- package/lib/commonjs/features/devConnect/nativeDevConnect.js +1 -103
- package/lib/commonjs/features/devConnect/nativeDevConnect.js.map +1 -1
- package/lib/commonjs/features/network/NetworkLogTab.js +91 -93
- package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
- package/lib/commonjs/features/network/index.js +7 -4
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js +1044 -0
- package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js.map +1 -0
- package/lib/commonjs/features/sessionHistory/index.js +103 -0
- package/lib/commonjs/features/sessionHistory/index.js.map +1 -0
- package/lib/commonjs/features/track/index.js +4 -3
- package/lib/commonjs/features/track/index.js.map +1 -1
- package/lib/commonjs/index.js +27 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/DebugView.js +3 -0
- package/lib/commonjs/ui/DebugView.js.map +1 -1
- package/lib/commonjs/ui/panel/DebugPanel.js +67 -34
- package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
- package/lib/commonjs/ui/panel/FeatureIntroCard.js +131 -0
- package/lib/commonjs/ui/panel/FeatureIntroCard.js.map +1 -0
- package/lib/commonjs/ui/panel/FeatureRail.js +163 -0
- package/lib/commonjs/ui/panel/FeatureRail.js.map +1 -0
- package/lib/commonjs/ui/panel/FloatPanelView.js +169 -32
- package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/commonjs/ui/panel/buildFeatureSummary.js +207 -0
- package/lib/commonjs/ui/panel/buildFeatureSummary.js.map +1 -0
- package/lib/commonjs/ui/panel/filterFeatureSnapshot.js +43 -0
- package/lib/commonjs/ui/panel/filterFeatureSnapshot.js.map +1 -0
- package/lib/commonjs/ui/panel/tabPersistence.js +17 -0
- package/lib/commonjs/ui/panel/tabPersistence.js.map +1 -0
- package/lib/commonjs/ui/theme/colors.js +6 -0
- package/lib/commonjs/ui/theme/colors.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +30 -8
- package/lib/commonjs/utils/DaemonClient.js.map +1 -1
- package/lib/commonjs/utils/SessionManager.js +132 -0
- package/lib/commonjs/utils/SessionManager.js.map +1 -0
- package/lib/commonjs/utils/StorageAdapter.js +104 -0
- package/lib/commonjs/utils/StorageAdapter.js.map +1 -0
- package/lib/commonjs/utils/createChannelFeature.js +22 -5
- package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
- package/lib/commonjs/utils/createDebugTab.js +21 -0
- package/lib/commonjs/utils/createDebugTab.js.map +1 -0
- package/lib/commonjs/utils/createPersistedObservableStore.js +14 -8
- package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/commonjs/utils/debugPreferences.js +28 -6
- package/lib/commonjs/utils/debugPreferences.js.map +1 -1
- package/lib/commonjs/utils/deviceReport.js +5 -1
- package/lib/commonjs/utils/deviceReport.js.map +1 -1
- package/lib/commonjs/utils/logRuntime.js +32 -0
- package/lib/commonjs/utils/logRuntime.js.map +1 -0
- package/lib/module/constants/logLevels.js +15 -0
- package/lib/module/constants/logLevels.js.map +1 -0
- package/lib/module/core/initialize.js +36 -18
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/features/console/ConsoleLogTab.js +1 -12
- package/lib/module/features/console/ConsoleLogTab.js.map +1 -1
- package/lib/module/features/console/index.js +15 -8
- package/lib/module/features/console/index.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectTab.js +21 -473
- package/lib/module/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/module/features/devConnect/devConnectPreferences.js +1 -12
- package/lib/module/features/devConnect/devConnectPreferences.js.map +1 -1
- package/lib/module/features/devConnect/devConnectUtils.js +1 -53
- package/lib/module/features/devConnect/devConnectUtils.js.map +1 -1
- package/lib/module/features/devConnect/index.js +5 -9
- package/lib/module/features/devConnect/index.js.map +1 -1
- package/lib/module/features/devConnect/nativeDevConnect.js +1 -100
- package/lib/module/features/devConnect/nativeDevConnect.js.map +1 -1
- package/lib/module/features/network/NetworkLogTab.js +91 -93
- package/lib/module/features/network/NetworkLogTab.js.map +1 -1
- package/lib/module/features/network/index.js +7 -4
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/sessionHistory/SessionHistoryTab.js +1039 -0
- package/lib/module/features/sessionHistory/SessionHistoryTab.js.map +1 -0
- package/lib/module/features/sessionHistory/index.js +99 -0
- package/lib/module/features/sessionHistory/index.js.map +1 -0
- package/lib/module/features/track/index.js +4 -3
- package/lib/module/features/track/index.js.map +1 -1
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/DebugView.js +3 -0
- package/lib/module/ui/DebugView.js.map +1 -1
- package/lib/module/ui/panel/DebugPanel.js +67 -34
- package/lib/module/ui/panel/DebugPanel.js.map +1 -1
- package/lib/module/ui/panel/FeatureIntroCard.js +126 -0
- package/lib/module/ui/panel/FeatureIntroCard.js.map +1 -0
- package/lib/module/ui/panel/FeatureRail.js +158 -0
- package/lib/module/ui/panel/FeatureRail.js.map +1 -0
- package/lib/module/ui/panel/FloatPanelView.js +170 -33
- package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/module/ui/panel/buildFeatureSummary.js +203 -0
- package/lib/module/ui/panel/buildFeatureSummary.js.map +1 -0
- package/lib/module/ui/panel/filterFeatureSnapshot.js +39 -0
- package/lib/module/ui/panel/filterFeatureSnapshot.js.map +1 -0
- package/lib/module/ui/panel/tabPersistence.js +13 -0
- package/lib/module/ui/panel/tabPersistence.js.map +1 -0
- package/lib/module/ui/theme/colors.js +6 -0
- package/lib/module/ui/theme/colors.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +30 -8
- package/lib/module/utils/DaemonClient.js.map +1 -1
- package/lib/module/utils/SessionManager.js +127 -0
- package/lib/module/utils/SessionManager.js.map +1 -0
- package/lib/module/utils/StorageAdapter.js +96 -0
- package/lib/module/utils/StorageAdapter.js.map +1 -0
- package/lib/module/utils/createChannelFeature.js +22 -5
- package/lib/module/utils/createChannelFeature.js.map +1 -1
- package/lib/module/utils/createDebugTab.js +17 -0
- package/lib/module/utils/createDebugTab.js.map +1 -0
- package/lib/module/utils/createPersistedObservableStore.js +14 -8
- package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/module/utils/debugPreferences.js +27 -6
- package/lib/module/utils/debugPreferences.js.map +1 -1
- package/lib/module/utils/deviceReport.js +4 -1
- package/lib/module/utils/deviceReport.js.map +1 -1
- package/lib/module/utils/logRuntime.js +26 -0
- package/lib/module/utils/logRuntime.js.map +1 -0
- package/lib/typescript/src/constants/logLevels.d.ts +3 -0
- package/lib/typescript/src/constants/logLevels.d.ts.map +1 -0
- package/lib/typescript/src/core/initialize.d.ts +6 -0
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/features/console/ConsoleLogTab.d.ts.map +1 -1
- package/lib/typescript/src/features/console/index.d.ts +2 -1
- package/lib/typescript/src/features/console/index.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectTab.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts +0 -2
- package/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts +0 -20
- package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/index.d.ts +2 -2
- package/lib/typescript/src/features/devConnect/index.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts +0 -25
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/types.d.ts +1 -3
- package/lib/typescript/src/features/devConnect/types.d.ts.map +1 -1
- package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
- package/lib/typescript/src/features/network/index.d.ts +2 -1
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts +20 -0
- package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts.map +1 -0
- package/lib/typescript/src/features/sessionHistory/index.d.ts +4 -0
- package/lib/typescript/src/features/sessionHistory/index.d.ts.map +1 -0
- package/lib/typescript/src/features/track/index.d.ts +2 -1
- package/lib/typescript/src/features/track/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/feature.d.ts +1 -1
- package/lib/typescript/src/types/feature.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +2 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/DebugView.d.ts +4 -2
- package/lib/typescript/src/ui/DebugView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts +3 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts +11 -0
- package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/FeatureRail.d.ts +16 -0
- package/lib/typescript/src/ui/panel/FeatureRail.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts +13 -0
- package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts +3 -0
- package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/tabPersistence.d.ts +3 -0
- package/lib/typescript/src/ui/panel/tabPersistence.d.ts.map +1 -0
- package/lib/typescript/src/ui/theme/colors.d.ts +5 -0
- package/lib/typescript/src/ui/theme/colors.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +7 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
- package/lib/typescript/src/utils/SessionManager.d.ts +30 -0
- package/lib/typescript/src/utils/SessionManager.d.ts.map +1 -0
- package/lib/typescript/src/utils/StorageAdapter.d.ts +38 -0
- package/lib/typescript/src/utils/StorageAdapter.d.ts.map +1 -0
- package/lib/typescript/src/utils/createChannelFeature.d.ts +2 -0
- package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
- package/lib/typescript/src/utils/createDebugTab.d.ts +18 -0
- package/lib/typescript/src/utils/createDebugTab.d.ts.map +1 -0
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +4 -1
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/debugPreferences.d.ts +1 -4
- package/lib/typescript/src/utils/debugPreferences.d.ts.map +1 -1
- package/lib/typescript/src/utils/deviceReport.d.ts +1 -0
- package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
- package/lib/typescript/src/utils/logRuntime.d.ts +13 -0
- package/lib/typescript/src/utils/logRuntime.d.ts.map +1 -0
- package/package.json +10 -6
- package/src/constants/logLevels.ts +13 -0
- package/src/core/initialize.ts +61 -21
- package/src/features/console/ConsoleLogTab.tsx +1 -14
- package/src/features/console/index.ts +18 -8
- package/src/features/devConnect/DevConnectTab.tsx +17 -381
- package/src/features/devConnect/devConnectPreferences.ts +0 -15
- package/src/features/devConnect/devConnectUtils.ts +1 -81
- package/src/features/devConnect/index.ts +2 -9
- package/src/features/devConnect/nativeDevConnect.ts +1 -136
- package/src/features/devConnect/types.ts +1 -3
- package/src/features/network/NetworkLogTab.tsx +61 -71
- package/src/features/network/index.ts +12 -3
- package/src/features/sessionHistory/SessionHistoryTab.tsx +691 -0
- package/src/features/sessionHistory/index.ts +102 -0
- package/src/features/track/index.ts +10 -3
- package/src/index.ts +13 -0
- package/src/types/feature.ts +2 -1
- package/src/types/index.ts +10 -0
- package/src/ui/DebugView.tsx +6 -1
- package/src/ui/panel/DebugPanel.tsx +60 -30
- package/src/ui/panel/FeatureIntroCard.tsx +127 -0
- package/src/ui/panel/FeatureRail.tsx +165 -0
- package/src/ui/panel/FloatPanelView.tsx +176 -25
- package/src/ui/panel/buildFeatureSummary.ts +288 -0
- package/src/ui/panel/filterFeatureSnapshot.ts +51 -0
- package/src/ui/panel/tabPersistence.ts +22 -0
- package/src/ui/theme/colors.ts +7 -0
- package/src/utils/DaemonClient.ts +33 -5
- package/src/utils/SessionManager.ts +174 -0
- package/src/utils/StorageAdapter.ts +135 -0
- package/src/utils/createChannelFeature.ts +28 -6
- package/src/utils/createDebugTab.ts +32 -0
- package/src/utils/createPersistedObservableStore.ts +18 -10
- package/src/utils/debugPreferences.ts +38 -8
- package/src/utils/deviceReport.ts +5 -1
- package/src/utils/logRuntime.ts +39 -0
- package/app.plugin.js +0 -51
- package/dev-client.js +0 -3
- package/lib/commonjs/ui/panel/FeatureTabBar.js +0 -182
- package/lib/commonjs/ui/panel/FeatureTabBar.js.map +0 -1
- package/lib/module/ui/panel/FeatureTabBar.js +0 -177
- package/lib/module/ui/panel/FeatureTabBar.js.map +0 -1
- package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts +0 -13
- package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts.map +0 -1
- package/scripts/bundle/android.js +0 -101
- package/scripts/bundle/cli.js +0 -57
- package/scripts/bundle/doctor.js +0 -38
- package/scripts/bundle/ios.js +0 -179
- package/scripts/bundle/setup.js +0 -39
- package/scripts/debug-bundle.gradle +0 -147
- package/src/ui/panel/FeatureTabBar.tsx +0 -204
package/scripts/bundle/cli.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function readOption(args, name) {
|
|
4
|
-
const index = args.indexOf(name);
|
|
5
|
-
return index >= 0 ? args[index + 1] : undefined;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function hasFlag(args, name) {
|
|
9
|
-
return args.includes(name);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function parseCommon(args, cwd) {
|
|
13
|
-
return {
|
|
14
|
-
cwd,
|
|
15
|
-
platform: readOption(args, '--platform') || 'all',
|
|
16
|
-
undo: hasFlag(args, '--undo'),
|
|
17
|
-
check: hasFlag(args, '--check'),
|
|
18
|
-
iosTarget: readOption(args, '--ios-target'),
|
|
19
|
-
yes: hasFlag(args, '--yes'),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function runBundleCli(args, deps = {}) {
|
|
24
|
-
const cwd = deps.cwd || process.cwd();
|
|
25
|
-
const io = deps.io || {
|
|
26
|
-
writeOut: (value) => process.stdout.write(value),
|
|
27
|
-
writeErr: (value) => process.stderr.write(value),
|
|
28
|
-
};
|
|
29
|
-
const command = args[0];
|
|
30
|
-
|
|
31
|
-
if (command === 'setup-bundle') {
|
|
32
|
-
const setupBundle = deps.setupBundle || require('./setup').setupBundle;
|
|
33
|
-
await setupBundle(parseCommon(args.slice(1), cwd));
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (command === 'doctor-bundle') {
|
|
38
|
-
const doctorBundle = deps.doctorBundle || require('./doctor').doctorBundle;
|
|
39
|
-
await doctorBundle({
|
|
40
|
-
cwd,
|
|
41
|
-
platform: readOption(args.slice(1), '--platform') || 'all',
|
|
42
|
-
app: readOption(args.slice(1), '--app'),
|
|
43
|
-
apk: readOption(args.slice(1), '--apk'),
|
|
44
|
-
});
|
|
45
|
-
return 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (command === 'embed') {
|
|
49
|
-
io.writeErr('Unknown command: embed\nUse: debug-toolkit setup-bundle\n');
|
|
50
|
-
return 1;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
io.writeErr(`Unknown command: ${command || '(none)'}\n`);
|
|
54
|
-
return 1;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
module.exports = { runBundleCli };
|
package/scripts/bundle/doctor.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execFileSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
function readZipEntriesWithUnzip(file) {
|
|
8
|
-
const output = execFileSync('unzip', ['-Z1', file], { encoding: 'utf8' });
|
|
9
|
-
return output.split(/\r?\n/).filter(Boolean);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function doctorIos(options) {
|
|
13
|
-
if (!options.app) throw new Error('--app is required for iOS doctor.');
|
|
14
|
-
const bundle = path.join(options.app, 'main.jsbundle');
|
|
15
|
-
if (!fs.existsSync(bundle)) {
|
|
16
|
-
throw new Error(`main.jsbundle not found in ${options.app}`);
|
|
17
|
-
}
|
|
18
|
-
return { ok: true, platform: 'ios', bundle };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function doctorAndroid(options) {
|
|
22
|
-
if (!options.apk) throw new Error('--apk is required for Android doctor.');
|
|
23
|
-
const readZipEntries = options.readZipEntries || readZipEntriesWithUnzip;
|
|
24
|
-
const entries = await readZipEntries(options.apk);
|
|
25
|
-
const bundle = entries.find((entry) => /^assets\/.+\.bundle$/.test(entry));
|
|
26
|
-
if (!bundle) {
|
|
27
|
-
throw new Error(`Android JS bundle not found in ${options.apk}`);
|
|
28
|
-
}
|
|
29
|
-
return { ok: true, platform: 'android', bundle };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function doctorBundle(options) {
|
|
33
|
-
if (options.platform === 'ios') return doctorIos(options);
|
|
34
|
-
if (options.platform === 'android') return doctorAndroid(options);
|
|
35
|
-
throw new Error(`Unsupported platform for doctor-bundle: ${options.platform}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = { doctorBundle, readZipEntriesWithUnzip };
|
package/scripts/bundle/ios.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const xcode = require('xcode');
|
|
6
|
-
|
|
7
|
-
const PHASE_NAME = 'Bundle React Native code and images';
|
|
8
|
-
const BEGIN = '# react-native-debug-toolkit: begin debug bundle';
|
|
9
|
-
const END = '# react-native-debug-toolkit: end debug bundle';
|
|
10
|
-
const BLOCK = `${BEGIN}\nexport FORCE_BUNDLING=1\n${END}`;
|
|
11
|
-
|
|
12
|
-
function stripQuotes(value) {
|
|
13
|
-
return String(value || '').replace(/^"|"$/g, '');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function findProjects(cwd) {
|
|
17
|
-
const iosDir = path.join(cwd, 'ios');
|
|
18
|
-
if (!fs.existsSync(iosDir)) {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return fs.readdirSync(iosDir)
|
|
23
|
-
.filter((entry) => entry.endsWith('.xcodeproj') && entry !== 'Pods.xcodeproj')
|
|
24
|
-
.map((entry) => path.join(iosDir, entry, 'project.pbxproj'))
|
|
25
|
-
.filter((file) => fs.existsSync(file));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function decodeScript(value) {
|
|
29
|
-
if (!value) {
|
|
30
|
-
return '';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
return JSON.parse(value);
|
|
35
|
-
} catch {
|
|
36
|
-
return stripQuotes(value);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function encodeScript(value) {
|
|
41
|
-
return JSON.stringify(value);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function phaseName(phase) {
|
|
45
|
-
return stripQuotes(phase.name || phase.comment);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isAppTarget(target) {
|
|
49
|
-
return stripQuotes(target.productType) === 'com.apple.product-type.application';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function targetName(target) {
|
|
53
|
-
return stripQuotes(target.name || target.productName || target.comment);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function targetBuildPhaseIds(target) {
|
|
57
|
-
return (target.buildPhases || []).map((phaseRef) => {
|
|
58
|
-
if (typeof phaseRef === 'string') {
|
|
59
|
-
return phaseRef;
|
|
60
|
-
}
|
|
61
|
-
return phaseRef.value;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function sectionEntries(objects, sectionName, isaName) {
|
|
66
|
-
const section = objects[sectionName];
|
|
67
|
-
if (section) {
|
|
68
|
-
return Object.entries(section)
|
|
69
|
-
.filter(([key]) => !key.endsWith('_comment'));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return Object.entries(objects)
|
|
73
|
-
.filter(([key, value]) => !key.endsWith('_comment') && value && value.isa === isaName)
|
|
74
|
-
.map(([key, value]) => [key, { ...value, comment: objects[`${key}_comment`] }]);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function loadTarget(cwd, iosTarget) {
|
|
78
|
-
const projects = findProjects(cwd);
|
|
79
|
-
if (projects.length !== 1) {
|
|
80
|
-
throw new Error(`Expected one Xcode project, found ${projects.length}. Pass --ios-target after selecting the app project.`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const pbxFile = projects[0];
|
|
84
|
-
const proj = xcode.project(pbxFile).parseSync();
|
|
85
|
-
const objects = proj.hash.project.objects;
|
|
86
|
-
const nativeTargets = sectionEntries(objects, 'PBXNativeTarget', 'PBXNativeTarget');
|
|
87
|
-
const shellPhases = new Map(sectionEntries(objects, 'PBXShellScriptBuildPhase', 'PBXShellScriptBuildPhase'));
|
|
88
|
-
|
|
89
|
-
const appTargets = nativeTargets
|
|
90
|
-
.filter(([, target]) => isAppTarget(target));
|
|
91
|
-
|
|
92
|
-
const matches = iosTarget
|
|
93
|
-
? appTargets.filter(([, target]) => targetName(target) === iosTarget)
|
|
94
|
-
: appTargets;
|
|
95
|
-
|
|
96
|
-
if (matches.length !== 1) {
|
|
97
|
-
throw new Error(`Expected one iOS app target, found ${matches.length}. Pass --ios-target <name>.`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const [, target] = matches[0];
|
|
101
|
-
for (const phaseId of targetBuildPhaseIds(target)) {
|
|
102
|
-
const phase = shellPhases.get(phaseId);
|
|
103
|
-
if (!phase || phaseName(phase) !== PHASE_NAME) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const script = decodeScript(phase.shellScript);
|
|
108
|
-
if (!script.includes('react-native-xcode.sh') && !script.includes('with-environment.sh')) {
|
|
109
|
-
throw new Error(`${PHASE_NAME} does not call React Native bundling script.`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { pbxFile, proj, phase };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
throw new Error(`${PHASE_NAME} phase not found on iOS app target.`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function insertBlock(script) {
|
|
119
|
-
if (script.includes(BEGIN)) {
|
|
120
|
-
return script;
|
|
121
|
-
}
|
|
122
|
-
return `${BLOCK}\n${script}`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function removeBlock(script) {
|
|
126
|
-
return script.replace(
|
|
127
|
-
/# react-native-debug-toolkit: begin debug bundle\r?\nexport FORCE_BUNDLING=1\r?\n# react-native-debug-toolkit: end debug bundle\r?\n?/g,
|
|
128
|
-
'',
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function writeProject(ctx, script) {
|
|
133
|
-
ctx.phase.shellScript = encodeScript(script);
|
|
134
|
-
fs.writeFileSync(ctx.pbxFile, ctx.proj.writeSync());
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function setupIosBundle(options) {
|
|
138
|
-
const ctx = loadTarget(options.cwd, options.iosTarget);
|
|
139
|
-
const script = decodeScript(ctx.phase.shellScript);
|
|
140
|
-
const next = insertBlock(script);
|
|
141
|
-
|
|
142
|
-
if (next === script) {
|
|
143
|
-
return { ok: true, changed: false };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
writeProject(ctx, next);
|
|
147
|
-
return { ok: true, changed: true };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function undoIosBundle(options) {
|
|
151
|
-
const ctx = loadTarget(options.cwd, options.iosTarget);
|
|
152
|
-
const script = decodeScript(ctx.phase.shellScript);
|
|
153
|
-
const next = removeBlock(script);
|
|
154
|
-
|
|
155
|
-
if (next === script) {
|
|
156
|
-
return { ok: true, changed: false };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
writeProject(ctx, next);
|
|
160
|
-
return { ok: true, changed: true };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function checkIosBundle(options) {
|
|
164
|
-
const ctx = loadTarget(options.cwd, options.iosTarget);
|
|
165
|
-
const script = decodeScript(ctx.phase.shellScript);
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
ok: script.includes(BLOCK),
|
|
169
|
-
changed: false,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = {
|
|
174
|
-
setupIosBundle,
|
|
175
|
-
undoIosBundle,
|
|
176
|
-
checkIosBundle,
|
|
177
|
-
BEGIN,
|
|
178
|
-
END,
|
|
179
|
-
};
|
package/scripts/bundle/setup.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { setupIosBundle, undoIosBundle, checkIosBundle } = require('./ios');
|
|
4
|
-
const { setupAndroidBundle, undoAndroidBundle, checkAndroidBundle } = require('./android');
|
|
5
|
-
|
|
6
|
-
async function setupBundle(options) {
|
|
7
|
-
const platforms = options.platform === 'all' ? ['ios', 'android'] : [options.platform];
|
|
8
|
-
const results = [];
|
|
9
|
-
|
|
10
|
-
for (const platform of platforms) {
|
|
11
|
-
if (platform === 'ios') {
|
|
12
|
-
if (options.undo) {
|
|
13
|
-
results.push(undoIosBundle(options));
|
|
14
|
-
} else if (options.check) {
|
|
15
|
-
results.push(checkIosBundle(options));
|
|
16
|
-
} else {
|
|
17
|
-
results.push(setupIosBundle(options));
|
|
18
|
-
}
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (platform === 'android') {
|
|
23
|
-
if (options.undo) {
|
|
24
|
-
results.push(undoAndroidBundle(options));
|
|
25
|
-
} else if (options.check) {
|
|
26
|
-
results.push(checkAndroidBundle(options));
|
|
27
|
-
} else {
|
|
28
|
-
results.push(setupAndroidBundle(options));
|
|
29
|
-
}
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return results;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = { setupBundle };
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// react-native-debug-toolkit: persistent debug/development bundle generation
|
|
2
|
-
|
|
3
|
-
import java.util.Locale
|
|
4
|
-
import javax.inject.Inject
|
|
5
|
-
import org.gradle.api.DefaultTask
|
|
6
|
-
import org.gradle.api.GradleException
|
|
7
|
-
import org.gradle.api.file.DirectoryProperty
|
|
8
|
-
import org.gradle.api.file.RegularFileProperty
|
|
9
|
-
import org.gradle.api.provider.ListProperty
|
|
10
|
-
import org.gradle.api.provider.Property
|
|
11
|
-
import org.gradle.api.tasks.Input
|
|
12
|
-
import org.gradle.api.tasks.InputDirectory
|
|
13
|
-
import org.gradle.api.tasks.InputFile
|
|
14
|
-
import org.gradle.api.tasks.Optional
|
|
15
|
-
import org.gradle.api.tasks.OutputDirectory
|
|
16
|
-
import org.gradle.api.tasks.TaskAction
|
|
17
|
-
import org.gradle.process.ExecOperations
|
|
18
|
-
|
|
19
|
-
abstract class DebugToolkitBundleTask extends DefaultTask {
|
|
20
|
-
@Inject
|
|
21
|
-
abstract ExecOperations getExecOperations()
|
|
22
|
-
|
|
23
|
-
@InputDirectory
|
|
24
|
-
abstract DirectoryProperty getRoot()
|
|
25
|
-
|
|
26
|
-
@InputFile
|
|
27
|
-
abstract RegularFileProperty getCliFile()
|
|
28
|
-
|
|
29
|
-
@InputFile
|
|
30
|
-
@Optional
|
|
31
|
-
abstract RegularFileProperty getEntryFile()
|
|
32
|
-
|
|
33
|
-
@InputFile
|
|
34
|
-
@Optional
|
|
35
|
-
abstract RegularFileProperty getBundleConfig()
|
|
36
|
-
|
|
37
|
-
@Input
|
|
38
|
-
abstract ListProperty<String> getNodeExecutableAndArgs()
|
|
39
|
-
|
|
40
|
-
@Input
|
|
41
|
-
abstract Property<String> getBundleCommand()
|
|
42
|
-
|
|
43
|
-
@Input
|
|
44
|
-
abstract Property<String> getBundleAssetName()
|
|
45
|
-
|
|
46
|
-
@Input
|
|
47
|
-
abstract ListProperty<String> getExtraPackagerArgs()
|
|
48
|
-
|
|
49
|
-
@OutputDirectory
|
|
50
|
-
abstract DirectoryProperty getJsBundleDir()
|
|
51
|
-
|
|
52
|
-
@OutputDirectory
|
|
53
|
-
abstract DirectoryProperty getResourcesDir()
|
|
54
|
-
|
|
55
|
-
@TaskAction
|
|
56
|
-
void run() {
|
|
57
|
-
def rootFile = root.get().asFile
|
|
58
|
-
def bundleOutput = new File(jsBundleDir.get().asFile, bundleAssetName.get())
|
|
59
|
-
def entry = resolveEntryFile(rootFile)
|
|
60
|
-
jsBundleDir.get().asFile.mkdirs()
|
|
61
|
-
resourcesDir.get().asFile.mkdirs()
|
|
62
|
-
|
|
63
|
-
def command = []
|
|
64
|
-
command.addAll(nodeExecutableAndArgs.get())
|
|
65
|
-
command.add(cliFile.get().asFile.absolutePath)
|
|
66
|
-
command.add(bundleCommand.get())
|
|
67
|
-
command.addAll([
|
|
68
|
-
"--platform", "android",
|
|
69
|
-
"--dev", "true",
|
|
70
|
-
"--entry-file", entry.absolutePath,
|
|
71
|
-
"--bundle-output", bundleOutput.absolutePath,
|
|
72
|
-
"--assets-dest", resourcesDir.get().asFile.absolutePath,
|
|
73
|
-
])
|
|
74
|
-
|
|
75
|
-
if (bundleConfig.isPresent()) {
|
|
76
|
-
command.add("--config")
|
|
77
|
-
command.add(bundleConfig.get().asFile.absolutePath)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
command.addAll(extraPackagerArgs.get())
|
|
81
|
-
|
|
82
|
-
execOperations.exec {
|
|
83
|
-
workingDir(rootFile)
|
|
84
|
-
commandLine(command)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private File resolveEntryFile(File rootFile) {
|
|
89
|
-
if (entryFile.isPresent()) {
|
|
90
|
-
return entryFile.get().asFile
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
def androidEntry = new File(rootFile, "index.android.js")
|
|
94
|
-
return androidEntry.exists() ? androidEntry : new File(rootFile, "index.js")
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
def toolkitCapitalize = { String value ->
|
|
99
|
-
value.length() == 0 ? value : value.substring(0, 1).toUpperCase(Locale.ROOT) + value.substring(1)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
def toolkitReactExt = project.extensions.findByName("react")
|
|
103
|
-
def toolkitAndroidComponents = project.extensions.findByName("androidComponents")
|
|
104
|
-
|
|
105
|
-
if (toolkitAndroidComponents == null) {
|
|
106
|
-
throw new GradleException("react-native-debug-toolkit debug-bundle.gradle requires the Android Gradle Plugin.")
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
def toolkitDefaultRoot = project.rootProject.layout.projectDirectory.dir("../")
|
|
110
|
-
def toolkitDebuggableVariants = toolkitReactExt?.debuggableVariants?.getOrElse(["debug"]) ?: ["debug"]
|
|
111
|
-
|
|
112
|
-
toolkitAndroidComponents.onVariants(toolkitAndroidComponents.selector().all()) { variant ->
|
|
113
|
-
def variantName = variant.name
|
|
114
|
-
def isDebuggable = toolkitDebuggableVariants.any { it.equalsIgnoreCase(variantName) }
|
|
115
|
-
|
|
116
|
-
if (!isDebuggable) {
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
def capName = toolkitCapitalize(variantName)
|
|
121
|
-
def bundleTask = tasks.register("createDebugToolkit${capName}JsAndAssets", DebugToolkitBundleTask) {
|
|
122
|
-
group = "react"
|
|
123
|
-
description = "Generate embedded React Native JS bundle for ${variantName} debug build."
|
|
124
|
-
|
|
125
|
-
root.set(toolkitReactExt?.root ?: toolkitDefaultRoot)
|
|
126
|
-
cliFile.set(toolkitReactExt?.cliFile ?: root.file("node_modules/react-native/cli.js"))
|
|
127
|
-
if (toolkitReactExt?.entryFile?.isPresent()) {
|
|
128
|
-
entryFile.set(toolkitReactExt.entryFile)
|
|
129
|
-
}
|
|
130
|
-
if (toolkitReactExt?.bundleConfig?.isPresent()) {
|
|
131
|
-
bundleConfig.set(toolkitReactExt.bundleConfig)
|
|
132
|
-
}
|
|
133
|
-
nodeExecutableAndArgs.set(toolkitReactExt?.nodeExecutableAndArgs ?: ["node"])
|
|
134
|
-
bundleCommand.set(toolkitReactExt?.bundleCommand ?: "bundle")
|
|
135
|
-
bundleAssetName.set(toolkitReactExt?.bundleAssetName ?: "index.android.bundle")
|
|
136
|
-
extraPackagerArgs.set(toolkitReactExt?.extraPackagerArgs ?: [])
|
|
137
|
-
jsBundleDir.set(project.layout.buildDirectory.dir("generated/assets/react-native-debug-toolkit/${variantName}"))
|
|
138
|
-
resourcesDir.set(project.layout.buildDirectory.dir("generated/res/react-native-debug-toolkit/${variantName}"))
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
variant.sources.assets?.addGeneratedSourceDirectory(bundleTask) { task ->
|
|
142
|
-
task.jsBundleDir
|
|
143
|
-
}
|
|
144
|
-
variant.sources.res?.addGeneratedSourceDirectory(bundleTask) { task ->
|
|
145
|
-
task.resourcesDir
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
Text,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Animated,
|
|
7
|
-
ScrollView,
|
|
8
|
-
TouchableOpacity,
|
|
9
|
-
} from 'react-native';
|
|
10
|
-
import { Colors } from '../theme/colors';
|
|
11
|
-
|
|
12
|
-
export interface TabItem {
|
|
13
|
-
id: string;
|
|
14
|
-
label: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface FeatureTabBarProps {
|
|
18
|
-
tabs: TabItem[];
|
|
19
|
-
activeIndex: number;
|
|
20
|
-
onSelectTab: (index: number) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function FeatureTabBar({ tabs, activeIndex, onSelectTab }: FeatureTabBarProps) {
|
|
24
|
-
const [underlineWidth, setUnderlineWidth] = useState(0);
|
|
25
|
-
const underlineTranslateX = useRef(new Animated.Value(0)).current;
|
|
26
|
-
const tabScrollViewRef = useRef<ScrollView>(null);
|
|
27
|
-
const tabLayouts = useRef<Array<{ x: number; width: number }>>([]);
|
|
28
|
-
const tabScrollOffset = useRef(0);
|
|
29
|
-
const tabViewportWidth = useRef(0);
|
|
30
|
-
const tabContentWidth = useRef(0);
|
|
31
|
-
const underlineInit = useRef(false);
|
|
32
|
-
const isSwitching = useRef(false);
|
|
33
|
-
|
|
34
|
-
const maxTabScroll = useCallback(
|
|
35
|
-
() => Math.max(0, tabContentWidth.current - tabViewportWidth.current),
|
|
36
|
-
[],
|
|
37
|
-
);
|
|
38
|
-
const clampScroll = useCallback(
|
|
39
|
-
(offset: number) => Math.min(Math.max(0, offset), maxTabScroll()),
|
|
40
|
-
[maxTabScroll],
|
|
41
|
-
);
|
|
42
|
-
const getTabScrollTarget = useCallback(
|
|
43
|
-
(index: number) => {
|
|
44
|
-
const layout = tabLayouts.current[index];
|
|
45
|
-
return layout ? clampScroll(layout.x - 60) : clampScroll(tabScrollOffset.current);
|
|
46
|
-
},
|
|
47
|
-
[clampScroll],
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const animateUnderline = useCallback(
|
|
51
|
-
(index: number, scrollOffset?: number) => {
|
|
52
|
-
const layout = tabLayouts.current[index];
|
|
53
|
-
if (!layout) return;
|
|
54
|
-
const offsetX = clampScroll(scrollOffset ?? tabScrollOffset.current);
|
|
55
|
-
setUnderlineWidth(layout.width);
|
|
56
|
-
Animated.spring(underlineTranslateX, {
|
|
57
|
-
toValue: layout.x - offsetX,
|
|
58
|
-
friction: 7,
|
|
59
|
-
tension: 50,
|
|
60
|
-
useNativeDriver: true,
|
|
61
|
-
}).start();
|
|
62
|
-
},
|
|
63
|
-
[clampScroll, underlineTranslateX],
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const syncUnderlineToActiveTab = useCallback(() => {
|
|
67
|
-
const layout = tabLayouts.current[activeIndex];
|
|
68
|
-
if (!layout) return;
|
|
69
|
-
const clamped = clampScroll(tabScrollOffset.current);
|
|
70
|
-
if (clamped !== tabScrollOffset.current) tabScrollOffset.current = clamped;
|
|
71
|
-
underlineTranslateX.setValue(layout.x - clamped);
|
|
72
|
-
setUnderlineWidth(layout.width);
|
|
73
|
-
}, [activeIndex, clampScroll, underlineTranslateX]);
|
|
74
|
-
|
|
75
|
-
const tryInit = useCallback(() => {
|
|
76
|
-
if (underlineInit.current || tabViewportWidth.current <= 0 || tabContentWidth.current <= 0)
|
|
77
|
-
return;
|
|
78
|
-
const layout = tabLayouts.current[activeIndex];
|
|
79
|
-
if (!layout) return;
|
|
80
|
-
const target = getTabScrollTarget(activeIndex);
|
|
81
|
-
tabScrollOffset.current = target;
|
|
82
|
-
tabScrollViewRef.current?.scrollTo({ x: target, animated: false });
|
|
83
|
-
underlineTranslateX.setValue(layout.x - target);
|
|
84
|
-
setUnderlineWidth(layout.width);
|
|
85
|
-
underlineInit.current = true;
|
|
86
|
-
}, [activeIndex, getTabScrollTarget, underlineTranslateX]);
|
|
87
|
-
|
|
88
|
-
const handleSelectTab = useCallback(
|
|
89
|
-
(index: number) => {
|
|
90
|
-
if (isSwitching.current || index === activeIndex) return;
|
|
91
|
-
isSwitching.current = true;
|
|
92
|
-
|
|
93
|
-
const layout = tabLayouts.current[index];
|
|
94
|
-
if (layout) {
|
|
95
|
-
const target = getTabScrollTarget(index);
|
|
96
|
-
tabScrollOffset.current = target;
|
|
97
|
-
tabScrollViewRef.current?.scrollTo({ x: target, animated: true });
|
|
98
|
-
animateUnderline(index, target);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
onSelectTab(index);
|
|
102
|
-
setTimeout(() => {
|
|
103
|
-
isSwitching.current = false;
|
|
104
|
-
}, 350);
|
|
105
|
-
},
|
|
106
|
-
[activeIndex, animateUnderline, getTabScrollTarget, onSelectTab],
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// React to external activeIndex changes (e.g. swipe-to-switch)
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (!underlineInit.current) return;
|
|
112
|
-
const layout = tabLayouts.current[activeIndex];
|
|
113
|
-
if (!layout) return;
|
|
114
|
-
const target = getTabScrollTarget(activeIndex);
|
|
115
|
-
tabScrollOffset.current = target;
|
|
116
|
-
tabScrollViewRef.current?.scrollTo({ x: target, animated: true });
|
|
117
|
-
animateUnderline(activeIndex, target);
|
|
118
|
-
}, [activeIndex, animateUnderline, getTabScrollTarget]);
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<View style={styles.container}>
|
|
122
|
-
<ScrollView
|
|
123
|
-
ref={tabScrollViewRef}
|
|
124
|
-
horizontal
|
|
125
|
-
showsHorizontalScrollIndicator={false}
|
|
126
|
-
contentContainerStyle={styles.scrollContent}
|
|
127
|
-
onLayout={(e) => {
|
|
128
|
-
tabViewportWidth.current = e.nativeEvent.layout.width;
|
|
129
|
-
tryInit();
|
|
130
|
-
}}
|
|
131
|
-
onContentSizeChange={(width) => {
|
|
132
|
-
tabContentWidth.current = width;
|
|
133
|
-
tryInit();
|
|
134
|
-
}}
|
|
135
|
-
onScroll={(e) => {
|
|
136
|
-
tabScrollOffset.current = clampScroll(e.nativeEvent.contentOffset.x);
|
|
137
|
-
if (!isSwitching.current) syncUnderlineToActiveTab();
|
|
138
|
-
}}
|
|
139
|
-
scrollEventThrottle={16}
|
|
140
|
-
>
|
|
141
|
-
{tabs.map((tab, index) => (
|
|
142
|
-
<TouchableOpacity
|
|
143
|
-
key={tab.id}
|
|
144
|
-
style={styles.tab}
|
|
145
|
-
onPress={() => handleSelectTab(index)}
|
|
146
|
-
activeOpacity={0.7}
|
|
147
|
-
onLayout={(e) => {
|
|
148
|
-
const { x, width } = e.nativeEvent.layout;
|
|
149
|
-
tabLayouts.current[index] = { x, width };
|
|
150
|
-
if (index === activeIndex) tryInit();
|
|
151
|
-
}}
|
|
152
|
-
>
|
|
153
|
-
<Text
|
|
154
|
-
style={[styles.tabText, activeIndex === index && styles.activeTabText]}
|
|
155
|
-
numberOfLines={1}
|
|
156
|
-
>
|
|
157
|
-
{tab.label}
|
|
158
|
-
</Text>
|
|
159
|
-
</TouchableOpacity>
|
|
160
|
-
))}
|
|
161
|
-
</ScrollView>
|
|
162
|
-
<Animated.View
|
|
163
|
-
style={[
|
|
164
|
-
styles.underline,
|
|
165
|
-
{ width: underlineWidth, transform: [{ translateX: underlineTranslateX }] },
|
|
166
|
-
]}
|
|
167
|
-
/>
|
|
168
|
-
</View>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const styles = StyleSheet.create({
|
|
173
|
-
container: {
|
|
174
|
-
backgroundColor: Colors.surface,
|
|
175
|
-
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
176
|
-
borderBottomColor: Colors.border,
|
|
177
|
-
},
|
|
178
|
-
scrollContent: {
|
|
179
|
-
paddingHorizontal: 20,
|
|
180
|
-
flexDirection: 'row',
|
|
181
|
-
},
|
|
182
|
-
tab: {
|
|
183
|
-
paddingVertical: 10,
|
|
184
|
-
paddingHorizontal: 14,
|
|
185
|
-
marginRight: 8,
|
|
186
|
-
},
|
|
187
|
-
tabText: {
|
|
188
|
-
fontSize: 14,
|
|
189
|
-
fontWeight: '500',
|
|
190
|
-
color: Colors.textLight,
|
|
191
|
-
},
|
|
192
|
-
activeTabText: {
|
|
193
|
-
color: Colors.primary,
|
|
194
|
-
fontWeight: '600',
|
|
195
|
-
},
|
|
196
|
-
underline: {
|
|
197
|
-
position: 'absolute',
|
|
198
|
-
bottom: 0,
|
|
199
|
-
left: 0,
|
|
200
|
-
height: 2.5,
|
|
201
|
-
borderRadius: 1.25,
|
|
202
|
-
backgroundColor: Colors.primary,
|
|
203
|
-
},
|
|
204
|
-
});
|