2026-04-22 16:54:51 +08:00
|
|
|
|
const { withAndroidManifest, withDangerousMod } = require('@expo/config-plugins');
|
2026-04-11 04:27:22 +08:00
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
|
|
|
|
const FOREGROUND_SERVICE_PERMISSION = 'android.permission.FOREGROUND_SERVICE';
|
|
|
|
|
|
const FOREGROUND_SERVICE_DATA_SYNC = 'android.permission.FOREGROUND_SERVICE_DATA_SYNC';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Expo Config Plugin:为 Android 添加前台服务保活
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能:
|
|
|
|
|
|
* 1. 添加 FOREGROUND_SERVICE 相关权限
|
|
|
|
|
|
* 2. 在 AndroidManifest 中声明 Service
|
|
|
|
|
|
* 3. 生成 Kotlin 原生服务代码
|
|
|
|
|
|
* 4. 生成 React Native Bridge 模块
|
|
|
|
|
|
*
|
|
|
|
|
|
* 参考:Element Android 的 GuardAndroidService
|
|
|
|
|
|
*/
|
|
|
|
|
|
const withForegroundService = (config, options = {}) => {
|
|
|
|
|
|
const {
|
2026-04-22 16:54:51 +08:00
|
|
|
|
notificationTitle = '威友',
|
2026-04-11 04:27:22 +08:00
|
|
|
|
notificationBody = '正在后台同步消息',
|
|
|
|
|
|
notificationIcon = 'ic_notification',
|
2026-04-22 16:54:51 +08:00
|
|
|
|
channelId = 'withyou_sync_foreground',
|
2026-04-11 04:27:22 +08:00
|
|
|
|
channelName = '消息同步',
|
|
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 添加权限和 Service 声明到 AndroidManifest
|
|
|
|
|
|
config = withAndroidManifest(config, (config) => {
|
|
|
|
|
|
const manifest = config.modResults;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加权限声明
|
|
|
|
|
|
const permissions = [
|
|
|
|
|
|
FOREGROUND_SERVICE_PERMISSION,
|
|
|
|
|
|
FOREGROUND_SERVICE_DATA_SYNC,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
permissions.forEach((permission) => {
|
|
|
|
|
|
const existingPermission = manifest.manifest['uses-permission']?.find(
|
|
|
|
|
|
(p) => p.$['android:name'] === permission
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!existingPermission) {
|
|
|
|
|
|
if (!manifest.manifest['uses-permission']) {
|
|
|
|
|
|
manifest.manifest['uses-permission'] = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
manifest.manifest['uses-permission'].push({
|
|
|
|
|
|
$: { 'android:name': permission },
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加 Service 声明
|
|
|
|
|
|
if (!manifest.manifest.application[0].service) {
|
|
|
|
|
|
manifest.manifest.application[0].service = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const existingService = manifest.manifest.application[0].service.find(
|
|
|
|
|
|
(s) => s.$['android:name'] === '.SyncForegroundService'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!existingService) {
|
|
|
|
|
|
manifest.manifest.application[0].service.push({
|
|
|
|
|
|
$: {
|
|
|
|
|
|
'android:name': '.SyncForegroundService',
|
|
|
|
|
|
'android:enabled': 'true',
|
|
|
|
|
|
'android:exported': 'false',
|
|
|
|
|
|
'android:foregroundServiceType': 'dataSync',
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return config;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 生成 Kotlin 原生代码
|
|
|
|
|
|
config = withDangerousMod(config, [
|
|
|
|
|
|
'android',
|
|
|
|
|
|
async (config) => {
|
|
|
|
|
|
const projectRoot = config.modRequest.projectRoot;
|
|
|
|
|
|
const packageName = config.android.package;
|
|
|
|
|
|
const kotlinPath = path.join(
|
|
|
|
|
|
projectRoot,
|
|
|
|
|
|
'android/app/src/main/java',
|
|
|
|
|
|
packageName.replace(/\./g, '/')
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 确保目录存在
|
|
|
|
|
|
if (!fs.existsSync(kotlinPath)) {
|
|
|
|
|
|
fs.mkdirSync(kotlinPath, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 SyncForegroundService.kt
|
|
|
|
|
|
const serviceCode = generateServiceCode(packageName, {
|
|
|
|
|
|
notificationTitle,
|
|
|
|
|
|
notificationBody,
|
|
|
|
|
|
notificationIcon,
|
|
|
|
|
|
channelId,
|
|
|
|
|
|
channelName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
|
|
path.join(kotlinPath, 'SyncForegroundService.kt'),
|
|
|
|
|
|
serviceCode
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 ForegroundServiceModule.kt (React Native Bridge)
|
|
|
|
|
|
const moduleCode = generateModuleCode(packageName);
|
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
|
|
path.join(kotlinPath, 'ForegroundServiceModule.kt'),
|
|
|
|
|
|
moduleCode
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 ForegroundServicePackage.kt
|
|
|
|
|
|
const packageCode = generatePackageCode(packageName);
|
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
|
|
path.join(kotlinPath, 'ForegroundServicePackage.kt'),
|
|
|
|
|
|
packageCode
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 修改 MainApplication.kt 注册模块
|
|
|
|
|
|
const mainAppPath = path.join(kotlinPath, 'MainApplication.kt');
|
|
|
|
|
|
if (fs.existsSync(mainAppPath)) {
|
|
|
|
|
|
let content = fs.readFileSync(mainAppPath, 'utf-8');
|
|
|
|
|
|
|
|
|
|
|
|
// 添加 import
|
|
|
|
|
|
if (!content.includes('ForegroundServicePackage')) {
|
|
|
|
|
|
content = content.replace(
|
|
|
|
|
|
/package .*\n/,
|
|
|
|
|
|
`$&\nimport ${packageName}.ForegroundServicePackage\n`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在 packages 列表中添加
|
|
|
|
|
|
if (!content.includes('ForegroundServicePackage()')) {
|
|
|
|
|
|
// 查找 PackageList(this).packages.apply 块
|
|
|
|
|
|
content = content.replace(
|
|
|
|
|
|
/(PackageList\(this\)\.packages\.apply\s*\{)/,
|
|
|
|
|
|
`$1\n // 前台服务模块\n add(ForegroundServicePackage())`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fs.writeFileSync(mainAppPath, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建通知图标目录和文件
|
|
|
|
|
|
const drawablePath = path.join(projectRoot, 'android/app/src/main/res/drawable');
|
|
|
|
|
|
if (!fs.existsSync(drawablePath)) {
|
|
|
|
|
|
fs.mkdirSync(drawablePath, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const iconPath = path.join(drawablePath, 'ic_notification.xml');
|
|
|
|
|
|
if (!fs.existsSync(iconPath)) {
|
|
|
|
|
|
fs.writeFileSync(iconPath, generateNotificationIcon());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return config;
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
return config;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成前台服务 Kotlin 代码
|
|
|
|
|
|
*/
|
|
|
|
|
|
function generateServiceCode(packageName, options) {
|
|
|
|
|
|
const { notificationTitle, notificationBody, notificationIcon, channelId, channelName } = options;
|
|
|
|
|
|
|
|
|
|
|
|
return `package ${packageName}
|
|
|
|
|
|
|
|
|
|
|
|
import android.app.Notification
|
|
|
|
|
|
import android.app.NotificationChannel
|
|
|
|
|
|
import android.app.NotificationManager
|
|
|
|
|
|
import android.app.PendingIntent
|
|
|
|
|
|
import android.app.Service
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
|
import android.content.pm.ServiceInfo
|
|
|
|
|
|
import android.os.Build
|
|
|
|
|
|
import android.os.IBinder
|
|
|
|
|
|
import androidx.core.app.NotificationCompat
|
|
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 前台同步服务
|
|
|
|
|
|
*
|
|
|
|
|
|
* 在通知栏显示常驻通知,防止应用进程被系统杀死
|
|
|
|
|
|
* 类似 Element Android 的 GuardAndroidService 和 V2Ray 的保活机制
|
|
|
|
|
|
*/
|
|
|
|
|
|
class SyncForegroundService : Service() {
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
const val NOTIFICATION_ID = 1001
|
|
|
|
|
|
const val CHANNEL_ID = "${channelId}"
|
|
|
|
|
|
const val CHANNEL_NAME = "${channelName}"
|
|
|
|
|
|
|
|
|
|
|
|
const val ACTION_START = "${packageName}.foreground.START"
|
|
|
|
|
|
const val ACTION_STOP = "${packageName}.foreground.STOP"
|
|
|
|
|
|
const val ACTION_UPDATE = "${packageName}.foreground.UPDATE"
|
|
|
|
|
|
|
|
|
|
|
|
const val EXTRA_TITLE = "title"
|
|
|
|
|
|
const val EXTRA_BODY = "body"
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动前台服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
fun start(context: Context, title: String = "${notificationTitle}", body: String = "${notificationBody}") {
|
|
|
|
|
|
val intent = Intent(context, SyncForegroundService::class.java).apply {
|
|
|
|
|
|
action = ACTION_START
|
|
|
|
|
|
putExtra(EXTRA_TITLE, title)
|
|
|
|
|
|
putExtra(EXTRA_BODY, body)
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
ContextCompat.startForegroundService(context, intent)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
android.util.Log.e("SyncForegroundService", "启动前台服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 停止前台服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
fun stop(context: Context) {
|
|
|
|
|
|
val intent = Intent(context, SyncForegroundService::class.java).apply {
|
|
|
|
|
|
action = ACTION_STOP
|
|
|
|
|
|
}
|
|
|
|
|
|
context.startService(intent)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新通知内容
|
|
|
|
|
|
*/
|
|
|
|
|
|
fun update(context: Context, title: String, body: String) {
|
|
|
|
|
|
val intent = Intent(context, SyncForegroundService::class.java).apply {
|
|
|
|
|
|
action = ACTION_UPDATE
|
|
|
|
|
|
putExtra(EXTRA_TITLE, title)
|
|
|
|
|
|
putExtra(EXTRA_BODY, body)
|
|
|
|
|
|
}
|
|
|
|
|
|
context.startService(intent)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private var notificationTitle: String = "${notificationTitle}"
|
|
|
|
|
|
private var notificationBody: String = "${notificationBody}"
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate() {
|
|
|
|
|
|
super.onCreate()
|
|
|
|
|
|
createNotificationChannel()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
|
|
|
|
when (intent?.action) {
|
|
|
|
|
|
ACTION_START -> {
|
|
|
|
|
|
notificationTitle = intent.getStringExtra(EXTRA_TITLE) ?: "${notificationTitle}"
|
|
|
|
|
|
notificationBody = intent.getStringExtra(EXTRA_BODY) ?: "${notificationBody}"
|
|
|
|
|
|
startForegroundCompat()
|
|
|
|
|
|
}
|
|
|
|
|
|
ACTION_STOP -> {
|
|
|
|
|
|
stopForegroundCompat()
|
|
|
|
|
|
stopSelf()
|
|
|
|
|
|
}
|
|
|
|
|
|
ACTION_UPDATE -> {
|
|
|
|
|
|
notificationTitle = intent.getStringExtra(EXTRA_TITLE) ?: notificationTitle
|
|
|
|
|
|
notificationBody = intent.getStringExtra(EXTRA_BODY) ?: notificationBody
|
|
|
|
|
|
updateNotification()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// START_STICKY: 服务被杀后自动重启
|
|
|
|
|
|
return START_STICKY
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onBind(intent: Intent?): IBinder? = null
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动前台服务(兼容 Android Q+)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startForegroundCompat() {
|
|
|
|
|
|
val notification = buildNotification()
|
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
|
|
|
|
startForeground(
|
|
|
|
|
|
NOTIFICATION_ID,
|
|
|
|
|
|
notification,
|
|
|
|
|
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
startForeground(NOTIFICATION_ID, notification)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 停止前台服务(兼容 Android N+)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun stopForegroundCompat() {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
|
|
|
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
|
|
stopForeground(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建通知渠道
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun createNotificationChannel() {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
|
|
val channel = NotificationChannel(
|
|
|
|
|
|
CHANNEL_ID,
|
|
|
|
|
|
CHANNEL_NAME,
|
|
|
|
|
|
NotificationManager.IMPORTANCE_MIN // 最低重要性,不打扰用户
|
|
|
|
|
|
).apply {
|
|
|
|
|
|
description = "后台消息同步服务"
|
|
|
|
|
|
setSound(null, null)
|
|
|
|
|
|
setShowBadge(false)
|
|
|
|
|
|
enableLights(false)
|
|
|
|
|
|
enableVibration(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val manager = getSystemService(NotificationManager::class.java)
|
|
|
|
|
|
manager.createNotificationChannel(channel)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构建通知
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun buildNotification(): Notification {
|
|
|
|
|
|
// 点击通知打开应用
|
|
|
|
|
|
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
|
|
|
|
val pendingIntent = PendingIntent.getActivity(
|
|
|
|
|
|
this,
|
|
|
|
|
|
0,
|
|
|
|
|
|
launchIntent,
|
|
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
|
|
|
|
|
.setContentTitle(notificationTitle)
|
|
|
|
|
|
.setContentText(notificationBody)
|
|
|
|
|
|
.setSmallIcon(R.drawable.${notificationIcon})
|
|
|
|
|
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_MIN)
|
|
|
|
|
|
.setOngoing(true) // 不可滑动清除
|
|
|
|
|
|
.setShowWhen(false)
|
|
|
|
|
|
.setContentIntent(pendingIntent)
|
|
|
|
|
|
.build()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新通知内容
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun updateNotification() {
|
|
|
|
|
|
val manager = getSystemService(NotificationManager::class.java)
|
|
|
|
|
|
manager.notify(NOTIFICATION_ID, buildNotification())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成 React Native Bridge 模块代码
|
|
|
|
|
|
*/
|
|
|
|
|
|
function generateModuleCode(packageName) {
|
|
|
|
|
|
return `package ${packageName}
|
|
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
|
|
|
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
|
|
|
|
import com.facebook.react.bridge.ReactMethod
|
|
|
|
|
|
import com.facebook.react.bridge.Promise
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* React Native Bridge: 前台服务控制模块
|
|
|
|
|
|
*/
|
|
|
|
|
|
class ForegroundServiceModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
|
|
|
|
|
|
|
|
|
|
override fun getName(): String = "ForegroundService"
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动前台服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
@ReactMethod
|
|
|
|
|
|
fun start(title: String, body: String, promise: Promise) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
SyncForegroundService.start(reactApplicationContext, title, body)
|
|
|
|
|
|
promise.resolve(true)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
promise.reject("FOREGROUND_SERVICE_ERROR", e.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 停止前台服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
@ReactMethod
|
|
|
|
|
|
fun stop(promise: Promise) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
SyncForegroundService.stop(reactApplicationContext)
|
|
|
|
|
|
promise.resolve(true)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
promise.reject("FOREGROUND_SERVICE_ERROR", e.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新通知内容
|
|
|
|
|
|
*/
|
|
|
|
|
|
@ReactMethod
|
|
|
|
|
|
fun update(title: String, body: String, promise: Promise) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
SyncForegroundService.update(reactApplicationContext, title, body)
|
|
|
|
|
|
promise.resolve(true)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
promise.reject("FOREGROUND_SERVICE_ERROR", e.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成 React Native Package 代码
|
|
|
|
|
|
*/
|
|
|
|
|
|
function generatePackageCode(packageName) {
|
|
|
|
|
|
return `package ${packageName}
|
|
|
|
|
|
|
|
|
|
|
|
import com.facebook.react.ReactPackage
|
|
|
|
|
|
import com.facebook.react.bridge.NativeModule
|
|
|
|
|
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
|
|
|
|
import com.facebook.react.uimanager.ViewManager
|
|
|
|
|
|
|
|
|
|
|
|
class ForegroundServicePackage : ReactPackage {
|
|
|
|
|
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
|
|
|
|
return listOf(ForegroundServiceModule(reactContext))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
|
|
|
|
return emptyList()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成通知图标 XML
|
|
|
|
|
|
*/
|
|
|
|
|
|
function generateNotificationIcon() {
|
|
|
|
|
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
|
|
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
|
|
|
android:width="24dp"
|
|
|
|
|
|
android:height="24dp"
|
|
|
|
|
|
android:viewportWidth="24"
|
|
|
|
|
|
android:viewportHeight="24">
|
|
|
|
|
|
<!-- 背景圆 -->
|
|
|
|
|
|
<path
|
|
|
|
|
|
android:fillColor="#4CAF50"
|
|
|
|
|
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
|
|
|
|
|
|
<!-- 对勾符号 -->
|
|
|
|
|
|
<path
|
|
|
|
|
|
android:fillColor="#FFFFFF"
|
|
|
|
|
|
android:pathData="M10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
|
|
|
|
|
|
</vector>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = withForegroundService;
|