diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 8f0816c..f676e31 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -209,6 +209,23 @@ jobs: sed -i '/always-auth/d' ~/.npmrc fi + - name: Cache Android NDK + uses: actions/cache@v4 + id: cache-ndk + with: + path: /opt/android/ndk/27.0.12077973 + key: ndk-27.0.12077973-v1 + + - name: Install Android NDK + if: steps.cache-ndk.outputs.cache-hit != 'true' + run: | + echo "Existing NDK versions:" + ls /opt/android/ndk/ 2>/dev/null || echo "No NDK dir" + echo "Installing NDK 27.0.12077973..." + yes | sdkmanager --install "ndk;27.0.12077973" + echo "NDK after install:" + ls /opt/android/ndk/ + - name: Cache Gradle uses: actions/cache@v4 with: @@ -360,6 +377,7 @@ jobs: expo.gif.enabled=true expo.webp.enabled=true expo.webp.animated=false + ndkVersion=27.1.12297006 expo.useLegacyPackaging=false PROPS_EOF diff --git a/app.json b/app.json index 7827cea..5529d14 100644 --- a/app.json +++ b/app.json @@ -151,7 +151,8 @@ "appKey": "2cbe4da12e2c24aa3dca4610", "channel": "developer-default" } - ] + ], + "./plugins/withHuaweiPush" ], "extra": { "eas": { diff --git a/plugins/agconnect-services.json b/plugins/agconnect-services.json new file mode 100644 index 0000000..cfe7357 --- /dev/null +++ b/plugins/agconnect-services.json @@ -0,0 +1 @@ +{"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"30086000618737115","product_id":"101653523864112478","client_id":"1949443370896080512","client_secret":"CA50EF8C69075FAEFECF2D9F0F3ED101DC3264E0BFE1CC5E5B0272E7FF8215A1","project_id":"101653523864112478","app_id":"117704941","api_key":"DgEDAIkxmMyXBT6vYYW8ZWWXBMJweDbdmmKLYo5MvgH2lV8jPSIZt5WVpjd0/nZ0dk9gAwHf+dWIySILr3QQd7W80H8NNvuCUAQPig==","package_name":"cn.qczlit.withyou"},"oauth_client":{"client_id":"117704941","client_type":1},"app_info":{"app_id":"117704941","package_name":"cn.qczlit.withyou"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"cn.qczlit.withyou","client":{"app_id":"117704941"},"oauth_client":{"client_id":"117704941","client_type":1},"app_info":{"app_id":"117704941","package_name":"cn.qczlit.withyou"}}]} \ No newline at end of file diff --git a/plugins/withHuaweiPush.js b/plugins/withHuaweiPush.js new file mode 100644 index 0000000..f373dd3 --- /dev/null +++ b/plugins/withHuaweiPush.js @@ -0,0 +1,110 @@ +const { + withAppBuildGradle, + withProjectBuildGradle, + withDangerousMod, + withAndroidManifest, +} = require('@expo/config-plugins'); +const fs = require('fs'); +const path = require('path'); + +const withHuaweiPush = (config, options = {}) => { + const { jpushVersion = '5.8.0' } = options; + + config = withProjectBuildGradle(config, (config) => { + const contents = config.modResults.contents; + + if (!contents.includes('developer.huawei.com/repo')) { + config.modResults.contents = contents.replace( + /(repositories\s*\{)/g, + (match) => { + return match + `\n maven { url 'https://developer.huawei.com/repo/' }`; + } + ); + } + + return config; + }); + + config = withProjectBuildGradle(config, (config) => { + const contents = config.modResults.contents; + + if (!contents.includes('com.huawei.agconnect:agcp')) { + if (contents.includes('buildscript')) { + const depsPattern = /(buildscript\s*\{[\s\S]*?dependencies\s*\{)/; + if (depsPattern.test(contents)) { + config.modResults.contents = contents.replace( + depsPattern, + `$1\n classpath 'com.huawei.agconnect:agcp:1.9.1.301'` + ); + } + } + } + + return config; + }); + + config = withAppBuildGradle(config, (config) => { + const contents = config.modResults.contents; + + if (!contents.includes('com.huawei.agconnect')) { + const applyPluginPattern = /apply plugin:\s*['"]com\.google\.gms\.google-services['"]/; + if (applyPluginPattern.test(contents)) { + config.modResults.contents = contents.replace( + applyPluginPattern, + `apply plugin: 'com.google.gms.google-services'\napply plugin: 'com.huawei.agconnect'` + ); + } else { + const lastApplyPlugin = /apply plugin:[^\n]*(?:\n)/g; + const matches = contents.match(lastApplyPlugin); + if (matches && matches.length > 0) { + const lastMatch = matches[matches.length - 1]; + const lastIndex = contents.lastIndexOf(lastMatch); + config.modResults.contents = + contents.slice(0, lastIndex + lastMatch.length) + + "apply plugin: 'com.huawei.agconnect'\n" + + contents.slice(lastIndex + lastMatch.length); + } else { + config.modResults.contents = contents + "\napply plugin: 'com.huawei.agconnect'\n"; + } + } + } + + if (!contents.includes('cn.jiguang.sdk.plugin:huawei')) { + const depsPattern = /dependencies\s*\{/; + if (depsPattern.test(config.modResults.contents)) { + config.modResults.contents = config.modResults.contents.replace( + depsPattern, + `dependencies {\n implementation 'cn.jiguang.sdk.plugin:huawei:${jpushVersion}'` + ); + } + } + + return config; + }); + + config = withDangerousMod(config, [ + 'android', + async (config) => { + const appDir = path.join(config.modRequest.platformProjectRoot, 'app'); + const srcFile = path.join(config.modRequest.projectRoot, 'plugins', 'agconnect-services.json'); + const destFile = path.join(appDir, 'agconnect-services.json'); + + if (fs.existsSync(srcFile)) { + fs.copyFileSync(srcFile, destFile); + console.log('[withHuaweiPush] copied agconnect-services.json to android/app/'); + } else { + console.warn('[withHuaweiPush] agconnect-services.json not found at project root'); + } + + return config; + }, + ]); + + config = withAndroidManifest(config, (config) => { + return config; + }); + + return config; +}; + +module.exports = withHuaweiPush; diff --git a/plugins/withJPush.js b/plugins/withJPush.js index dac4abe..6893fde 100644 --- a/plugins/withJPush.js +++ b/plugins/withJPush.js @@ -17,19 +17,40 @@ const withJPush = (config, options = {}) => { // 1. Android: Inject manifestPlaceholders into app/build.gradle config = withAppBuildGradle(config, (config) => { - const block = '\n manifestPlaceholders = [\n' + - ' JPUSH_APPKEY: "' + appKey + '",\n' + - ' JPUSH_CHANNEL: "' + channel + '"\n' + - ' ]'; - const contents = config.modResults.contents; - if (contents.includes('manifestPlaceholders')) { - config.modResults.contents = contents.replace( - /manifestPlaceholders\s*=\s*\[[\s\S]*?\]/, - block.trim() - ); + const requiredPlaceholders = { + JPUSH_APPKEY: appKey, + JPUSH_CHANNEL: channel, + }; + + const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/; + const match = contents.match(manifestPlaceholderRegex); + + if (match) { + const existingBlock = match[1]; + const existingEntries = {}; + const entryRegex = /(\w+)\s*:\s*"([^"]*)"/g; + let entryMatch; + while ((entryMatch = entryRegex.exec(existingBlock)) !== null) { + existingEntries[entryMatch[1]] = entryMatch[2]; + } + + for (const [key, value] of Object.entries(requiredPlaceholders)) { + existingEntries[key] = value; + } + + const entriesStr = Object.entries(existingEntries) + .map(([k, v]) => `${k}: "${v}"`) + .join(',\n '); + const newBlock = `\n manifestPlaceholders = [\n ${entriesStr}\n ]`; + + config.modResults.contents = contents.replace(manifestPlaceholderRegex, newBlock.trim()); } else if (contents.includes('REACT_NATIVE_RELEASE_LEVEL')) { + const entriesStr = Object.entries(requiredPlaceholders) + .map(([k, v]) => `${k}: "${v}"`) + .join(',\n '); + const block = '\n manifestPlaceholders = [\n ' + entriesStr + '\n ]'; const lines = contents.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].includes('REACT_NATIVE_RELEASE_LEVEL')) {