refactor(plugins): update JPush config and improve SmartImage loading
- Update `withJPush` plugin to support Expo SDK 56+ by using `JPUSHService.setup` instead of `JCoreModule.setup` and adding `UNUserNotificationCenterDelegate` conformance. - Improve `SmartImage` component by adding a ref to prevent loading state flickering when transitioning from preview to original image. - Upgrade `@shopify/flash-list` dependency. - Add transition prop to `SmartImage` for smoother image loading.
This commit is contained in:
22
package-lock.json
generated
22
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"@livekit/react-native": "^2.11.0",
|
||||
"@livekit/react-native-webrtc": "^144.1.0",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@shopify/flash-list": "2.0.2",
|
||||
"@shopify/flash-list": "^2.3.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"axios": "^1.13.6",
|
||||
"date-fns": "^4.1.0",
|
||||
@@ -3359,13 +3359,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shopify/flash-list": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@shopify/flash-list/-/flash-list-2.0.2.tgz",
|
||||
"integrity": "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@shopify/flash-list/-/flash-list-2.3.1.tgz",
|
||||
"integrity": "sha512-7oktg2NQR7KAODjFoDaWe8/OBzyYbdTE3zQTrUBMxjIbxHTHN7UXRX1hX3DHk8KvtkgQdRfZOV8Gjj2l4fGrXw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": "*",
|
||||
"react": "*",
|
||||
@@ -12037,17 +12034,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/usehooks-ts": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@livekit/react-native": "^2.11.0",
|
||||
"@livekit/react-native-webrtc": "^144.1.0",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@shopify/flash-list": "2.0.2",
|
||||
"@shopify/flash-list": "^2.3.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"axios": "^1.13.6",
|
||||
"date-fns": "^4.1.0",
|
||||
@@ -78,7 +78,9 @@
|
||||
"private": true,
|
||||
"expo": {
|
||||
"install": {
|
||||
"exclude": ["expo-image"]
|
||||
"exclude": [
|
||||
"expo-image"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ const withJPush = (config, options = {}) => {
|
||||
config = withAppDelegate(config, (config) => {
|
||||
const { contents } = config.modResults;
|
||||
|
||||
if (contents.includes('JPush') || contents.includes('JCoreModule')) {
|
||||
if (contents.includes('JPush initialization') && contents.includes('JPUSHService.registerDeviceToken')) {
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,8 @@ const withJPush = (config, options = {}) => {
|
||||
if (isSwift) {
|
||||
let newContents = contents;
|
||||
|
||||
if (!newContents.includes('import JPushRN')) {
|
||||
// Add UNUserNotifications import if needed
|
||||
if (!newContents.includes('import UserNotifications')) {
|
||||
if (newContents.includes('import UIKit')) {
|
||||
newContents = newContents.replace(
|
||||
/(import UIKit\n)/,
|
||||
@@ -118,56 +119,62 @@ const withJPush = (config, options = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Swift: Add JPush init in application(_:didFinishLaunchingWithOptions:)
|
||||
// Match various Swift signatures
|
||||
const swiftDidFinishPatterns = [
|
||||
/public override func application\(\s*_ application: UIApplication,\s*didFinishLaunchingWithOptions launchOptions: \[UIApplication\.LaunchOptionsKey: Any\]\? = nil\s*\) -> Bool \{/,
|
||||
/func application\(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: \[UIApplication\.LaunchOptionsKey: Any\]\?\?\) -> Bool \{/,
|
||||
];
|
||||
|
||||
let didInsertInit = false;
|
||||
for (const pattern of swiftDidFinishPatterns) {
|
||||
if (pattern.test(newContents)) {
|
||||
// Add UNUserNotificationCenterDelegate conformance if needed
|
||||
if (!newContents.includes('UNUserNotificationCenterDelegate')) {
|
||||
newContents = newContents.replace(
|
||||
pattern,
|
||||
`$&\n // JPush initialization\n #if DEBUG\n JCoreModule.setup(withOption: launchOptions, appKey: "${appKey}", channel: "${channel}", apsForProduction: false)\n #else\n JCoreModule.setup(withOption: launchOptions, appKey: "${appKey}", channel: "${channel}", apsForProduction: true)\n #endif`
|
||||
/class AppDelegate: ExpoAppDelegate \{/,
|
||||
'class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate {'
|
||||
);
|
||||
didInsertInit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: find the didFinishLaunchingWithOptions method start and insert after the opening brace
|
||||
if (!didInsertInit && newContents.includes('didFinishLaunchingWithOptions')) {
|
||||
// Swift: Add JPush init in application(_:didFinishLaunchingWithOptions:)
|
||||
// Expo SDK 56+ uses JPUSHService.setup (not JCoreModule.setup)
|
||||
if (!newContents.includes('JPUSHService.setup') && !newContents.includes('JCoreModule.setup')) {
|
||||
const jpushInitBlock = `
|
||||
// JPush initialization
|
||||
#if DEBUG
|
||||
JPUSHService.setup(withOption: launchOptions, appKey: "${appKey}", channel: "${channel}", apsForProduction: false)
|
||||
#else
|
||||
JPUSHService.setup(withOption: launchOptions, appKey: "${appKey}", channel: "${channel}", apsForProduction: true)
|
||||
#endif
|
||||
UNUserNotificationCenter.current().delegate = self`;
|
||||
|
||||
// Insert after the opening brace of didFinishLaunchingWithOptions
|
||||
// The { may be on the same line as didFinishLaunchingWithOptions or on a subsequent line
|
||||
if (newContents.includes('didFinishLaunchingWithOptions')) {
|
||||
const lines = newContents.split('\n');
|
||||
let foundDidFinish = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes('didFinishLaunchingWithOptions') && lines[i].includes('{')) {
|
||||
lines.splice(i + 1, 0,
|
||||
' // JPush initialization',
|
||||
' #if DEBUG',
|
||||
` JPUSHService.setup(withOption: launchOptions, appKey: "${appKey}", channel: "${channel}", apsForProduction: false)`,
|
||||
' #else',
|
||||
` JPUSHService.setup(withOption: launchOptions, appKey: "${appKey}", channel: "${channel}", apsForProduction: true)`,
|
||||
' #endif',
|
||||
' UNUserNotificationCenter.current().delegate = self'
|
||||
);
|
||||
if (lines[i].includes('didFinishLaunchingWithOptions')) {
|
||||
foundDidFinish = true;
|
||||
}
|
||||
// Insert after the line that contains the opening brace of this method
|
||||
if (foundDidFinish && lines[i].includes('{')) {
|
||||
const indent = lines[i].match(/^(\s*)/)[1];
|
||||
const initLines = jpushInitBlock.split('\n').map(l => l ? indent + l.trimStart() : l);
|
||||
lines.splice(i + 1, 0, ...initLines);
|
||||
newContents = lines.join('\n');
|
||||
didInsertInit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (newContents.includes('JCoreModule.setup') && !newContents.includes('JPUSHService.setup')) {
|
||||
// Migrate from JCoreModule.setup to JPUSHService.setup
|
||||
newContents = newContents.replace(/JCoreModule\.setup/g, 'JPUSHService.setup');
|
||||
}
|
||||
|
||||
// Also add delegate = self after regex-based insertion if not already present
|
||||
if (didInsertInit && !newContents.includes('UNUserNotificationCenter.current().delegate')) {
|
||||
// Add delegate = self after init insertion if not present
|
||||
if (!newContents.includes('UNUserNotificationCenter.current().delegate')) {
|
||||
newContents = newContents.replace(
|
||||
/(#endif\n)(\s+let delegate)/,
|
||||
`$1 UNUserNotificationCenter.current().delegate = self\n$2`
|
||||
);
|
||||
}
|
||||
|
||||
// Swift: Add delegate methods before the closing brace of the class
|
||||
// Swift: Add JPush notification delegate methods to AppDelegate class (before closing brace)
|
||||
// These go in AppDelegate, NOT ReactNativeDelegate
|
||||
const jpushSwiftMethods = `
|
||||
|
||||
// JPush: Register for remote notifications
|
||||
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
JPUSHService.registerDeviceToken(deviceToken)
|
||||
@@ -195,12 +202,22 @@ const withJPush = (config, options = {}) => {
|
||||
`;
|
||||
|
||||
if (!newContents.includes('JPUSHService.registerDeviceToken')) {
|
||||
// Find the closing brace of the AppDelegate class
|
||||
// Insert before the closing brace of the AppDelegate class
|
||||
// Find the last '}' that closes the AppDelegate class (before ReactNativeDelegate)
|
||||
const classEndPattern = /\n\}\n\nclass ReactNativeDelegate/;
|
||||
if (classEndPattern.test(newContents)) {
|
||||
newContents = newContents.replace(
|
||||
classEndPattern,
|
||||
`${jpushSwiftMethods}}\n\nclass ReactNativeDelegate`
|
||||
);
|
||||
} else {
|
||||
// Fallback: insert before the last closing brace
|
||||
const lastBraceIndex = newContents.lastIndexOf('}');
|
||||
if (lastBraceIndex !== -1) {
|
||||
newContents = newContents.slice(0, lastBraceIndex) + jpushSwiftMethods + '\n' + newContents.slice(lastBraceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.modResults.contents = newContents;
|
||||
} else {
|
||||
|
||||
@@ -125,6 +125,7 @@ export const SmartImage: React.FC<SmartImageProps> = ({
|
||||
const [loadState, setLoadState] = useState<ImageLoadState>('loading');
|
||||
const [isVisible, setIsVisible] = useState(() => !lazyLoad || Platform.OS !== 'web');
|
||||
const [shouldLoadOriginal, setShouldLoadOriginal] = useState(false);
|
||||
const isUpgradingFromPreview = useRef(false);
|
||||
const lazyTargetRef = useRef<View>(null);
|
||||
|
||||
// 解析图片源 - 支持 uri 或 url 字段
|
||||
@@ -137,6 +138,7 @@ export const SmartImage: React.FC<SmartImageProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
setShouldLoadOriginal(false);
|
||||
isUpgradingFromPreview.current = false;
|
||||
setLoadState('loading');
|
||||
}, [imageUri]);
|
||||
|
||||
@@ -180,16 +182,24 @@ export const SmartImage: React.FC<SmartImageProps> = ({
|
||||
|
||||
// 处理加载开始
|
||||
const handleLoadStart = useCallback(() => {
|
||||
// 从预览图切换到原图时,不重置为loading状态,避免闪烁
|
||||
if (isUpgradingFromPreview.current) {
|
||||
return;
|
||||
}
|
||||
setLoadState('loading');
|
||||
}, []);
|
||||
|
||||
// 处理加载完成
|
||||
const handleLoad = useCallback(() => {
|
||||
setLoadState('success');
|
||||
// 如果正在加载预览图,切换到原图
|
||||
// 如果正在加载预览图,切换到原图(不重置loading状态,避免闪烁)
|
||||
if (usePreview && !shouldLoadOriginal && previewUri && getImageUrl() === previewUri) {
|
||||
isUpgradingFromPreview.current = true;
|
||||
setShouldLoadOriginal(true);
|
||||
return;
|
||||
}
|
||||
// 原图加载完成(包括从预览图升级的情况)
|
||||
isUpgradingFromPreview.current = false;
|
||||
setLoadState('success');
|
||||
onLoad?.();
|
||||
}, [onLoad, usePreview, shouldLoadOriginal, previewUri, getImageUrl]);
|
||||
|
||||
@@ -198,9 +208,11 @@ export const SmartImage: React.FC<SmartImageProps> = ({
|
||||
(error: any) => {
|
||||
// 如果预览图加载失败,尝试加载原图
|
||||
if (usePreview && !shouldLoadOriginal && getImageUrl() === previewUri && imageUri) {
|
||||
isUpgradingFromPreview.current = true;
|
||||
setShouldLoadOriginal(true);
|
||||
return;
|
||||
}
|
||||
isUpgradingFromPreview.current = false;
|
||||
setLoadState('error');
|
||||
onError?.(error);
|
||||
},
|
||||
@@ -298,6 +310,7 @@ export const SmartImage: React.FC<SmartImageProps> = ({
|
||||
resizeMode === 'center' ? 'scale-down' :
|
||||
resizeMode
|
||||
}
|
||||
transition={200}
|
||||
cachePolicy={cachePolicy}
|
||||
onLoadStart={handleLoadStart}
|
||||
onLoad={handleLoad}
|
||||
|
||||
Reference in New Issue
Block a user