From 067bba0814b1a8d563617423a23b6b1ea63cf3f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=BE=9C=E5=A3=B0=E4=B8=96=E7=BA=AA?= <>
Date: Thu, 18 Dec 2025 17:24:24 +0800
Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81=E6=B6=88=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
NotificationService/Info.plist | 13 ++
NotificationService/NotificationService.swift | 69 +++++++
ReaderHive.xcodeproj/project.pbxproj | 172 +++++++++++++++++-
.../xcschemes/NotificationService.xcscheme | 98 ++++++++++
ReaderHive/Source/Info.plist | 4 +
5 files changed, 355 insertions(+), 1 deletion(-)
create mode 100644 NotificationService/Info.plist
create mode 100644 NotificationService/NotificationService.swift
create mode 100644 ReaderHive.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme
diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist
new file mode 100644
index 0000000..57421eb
--- /dev/null
+++ b/NotificationService/Info.plist
@@ -0,0 +1,13 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.usernotifications.service
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).NotificationService
+
+
+
diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift
new file mode 100644
index 0000000..0b963dd
--- /dev/null
+++ b/NotificationService/NotificationService.swift
@@ -0,0 +1,69 @@
+//
+// NotificationService.swift
+// NotificationService
+//
+// Created by 澜声世纪 on 2025/12/18.
+//
+
+import UserNotifications
+
+class NotificationService: UNNotificationServiceExtension {
+
+ var contentHandler: ((UNNotificationContent) -> Void)?
+ var bestAttemptContent: UNMutableNotificationContent?
+
+ override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
+
+ self.contentHandler = contentHandler
+ bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
+
+ guard let bestAttemptContent = bestAttemptContent else {
+ contentHandler(request.content)
+ return
+ }
+
+ if let options = request.content.userInfo["fcm_options"] as? [String : Any],
+ let imageURLString = options["image"] as? String,
+ let imageURL = URL(string: imageURLString) {
+
+ downloadImage(from: imageURL) { attachment in
+ if let attachment = attachment {
+ bestAttemptContent.attachments = [attachment]
+ }
+ contentHandler(bestAttemptContent)
+ }
+ } else {
+ contentHandler(bestAttemptContent)
+ }
+ }
+
+ override func serviceExtensionTimeWillExpire() {
+ // Called just before the extension will be terminated by the system.
+ // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
+ if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
+ contentHandler(bestAttemptContent)
+ }
+ }
+
+ private func downloadImage(from url: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
+ let task = URLSession.shared.downloadTask(with: url) { (downloadedUrl, _, error) in
+ guard let downloadedUrl = downloadedUrl else {
+ completion(nil)
+ return
+ }
+
+ let tmpDir = FileManager.default.temporaryDirectory
+ let tmpFile = tmpDir.appendingPathComponent(url.lastPathComponent)
+
+ do {
+ try FileManager.default.moveItem(at: downloadedUrl, to: tmpFile)
+ let attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: tmpFile, options: nil)
+ completion(attachment)
+ } catch {
+ completion(nil)
+ }
+ }
+ task.resume()
+ }
+
+}
diff --git a/ReaderHive.xcodeproj/project.pbxproj b/ReaderHive.xcodeproj/project.pbxproj
index e558c5b..10b8f2d 100644
--- a/ReaderHive.xcodeproj/project.pbxproj
+++ b/ReaderHive.xcodeproj/project.pbxproj
@@ -87,6 +87,8 @@
85606A9A2EEBC26F005D989D /* NRCoinsPackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85606A992EEBC26F005D989D /* NRCoinsPackViewController.swift */; };
85606A9C2EEBE243005D989D /* NRCoinsPackHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85606A9B2EEBE243005D989D /* NRCoinsPackHeaderView.swift */; };
85606A9F2EEBE95A005D989D /* NRDashedLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85606A9E2EEBE95A005D989D /* NRDashedLineView.swift */; };
+ 85859E5C2EF3FC5F0020D282 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 85859E552EF3FC5F0020D282 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 85859E662EF3FC6E0020D282 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85859E632EF3FC6E0020D282 /* NotificationService.swift */; };
85CF941D2EEBFEA6006467E3 /* NRCoinsPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF941C2EEBFEA6006467E3 /* NRCoinsPackModel.swift */; };
85CF941F2EEBFECF006467E3 /* NRCoinsPackReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF941E2EEBFECF006467E3 /* NRCoinsPackReceiveModel.swift */; };
85CF94212EEC050D006467E3 /* NRCoinsPackBuyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CF94202EEC050D006467E3 /* NRCoinsPackBuyView.swift */; };
@@ -473,6 +475,30 @@
F3B859952EEA64430095A9CC /* NROrderRecordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859942EEA64430095A9CC /* NROrderRecordsCell.swift */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ 85859E5A2EF3FC5F0020D282 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 03980F5D2ED009E30006E317 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 85859E542EF3FC5F0020D282;
+ remoteInfo = NotificationService;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 85859E5D2EF3FC5F0020D282 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ 85859E5C2EF3FC5F0020D282 /* NotificationService.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
0373D93F2ED57B1C0017DCC7 /* NRNovelDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailViewController.swift; sourceTree = ""; };
0373D9442ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailViewModel.swift; sourceTree = ""; };
@@ -559,6 +585,9 @@
85606A992EEBC26F005D989D /* NRCoinsPackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCoinsPackViewController.swift; sourceTree = ""; };
85606A9B2EEBE243005D989D /* NRCoinsPackHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCoinsPackHeaderView.swift; sourceTree = ""; };
85606A9E2EEBE95A005D989D /* NRDashedLineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRDashedLineView.swift; sourceTree = ""; };
+ 85859E552EF3FC5F0020D282 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 85859E622EF3FC6E0020D282 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 85859E632EF3FC6E0020D282 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; };
85CF941C2EEBFEA6006467E3 /* NRCoinsPackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCoinsPackModel.swift; sourceTree = ""; };
85CF941E2EEBFECF006467E3 /* NRCoinsPackReceiveModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCoinsPackReceiveModel.swift; sourceTree = ""; };
85CF94202EEC050D006467E3 /* NRCoinsPackBuyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCoinsPackBuyView.swift; sourceTree = ""; };
@@ -956,6 +985,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 85859E522EF3FC5F0020D282 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -1055,6 +1091,7 @@
isa = PBXGroup;
children = (
03980F862ED009EB0006E317 /* ReaderHive */,
+ 85859E642EF3FC6E0020D282 /* NotificationService */,
03980F662ED009E30006E317 /* Products */,
E0C2D753F0B7D561FB16D3E3 /* Pods */,
7508E82FE7A8A9ED3762CFF3 /* Frameworks */,
@@ -1065,6 +1102,7 @@
isa = PBXGroup;
children = (
03980F652ED009E30006E317 /* ReaderHive.app */,
+ 85859E552EF3FC5F0020D282 /* NotificationService.appex */,
);
name = Products;
sourceTree = "";
@@ -1343,6 +1381,15 @@
name = Frameworks;
sourceTree = "";
};
+ 85859E642EF3FC6E0020D282 /* NotificationService */ = {
+ isa = PBXGroup;
+ children = (
+ 85859E622EF3FC6E0020D282 /* Info.plist */,
+ 85859E632EF3FC6E0020D282 /* NotificationService.swift */,
+ );
+ path = NotificationService;
+ sourceTree = "";
+ };
85CF94682EF3CC12006467E3 /* Other */ = {
isa = PBXGroup;
children = (
@@ -2317,16 +2364,37 @@
03980F622ED009E30006E317 /* Frameworks */,
03980F632ED009E30006E317 /* Resources */,
CA7F5C24E1300D43A77AF33C /* [CP] Embed Pods Frameworks */,
+ 85859E5D2EF3FC5F0020D282 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
+ 85859E5B2EF3FC5F0020D282 /* PBXTargetDependency */,
);
name = ReaderHive;
productName = NovelReader;
productReference = 03980F652ED009E30006E317 /* ReaderHive.app */;
productType = "com.apple.product-type.application";
};
+ 85859E542EF3FC5F0020D282 /* NotificationService */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 85859E612EF3FC5F0020D282 /* Build configuration list for PBXNativeTarget "NotificationService" */;
+ buildPhases = (
+ 85859E512EF3FC5F0020D282 /* Sources */,
+ 85859E522EF3FC5F0020D282 /* Frameworks */,
+ 85859E532EF3FC5F0020D282 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = NotificationService;
+ packageProductDependencies = (
+ );
+ productName = NotificationService;
+ productReference = 85859E552EF3FC5F0020D282 /* NotificationService.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -2335,12 +2403,15 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
CLASSPREFIX = NR;
- LastSwiftUpdateCheck = 2600;
+ LastSwiftUpdateCheck = 2610;
LastUpgradeCheck = 2600;
TargetAttributes = {
03980F642ED009E30006E317 = {
CreatedOnToolsVersion = 26.0.1;
};
+ 85859E542EF3FC5F0020D282 = {
+ CreatedOnToolsVersion = 26.1.1;
+ };
};
};
buildConfigurationList = 03980F602ED009E30006E317 /* Build configuration list for PBXProject "ReaderHive" */;
@@ -2362,6 +2433,7 @@
projectRoot = "";
targets = (
03980F642ED009E30006E317 /* ReaderHive */,
+ 85859E542EF3FC5F0020D282 /* NotificationService */,
);
};
/* End PBXProject section */
@@ -2382,6 +2454,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 85859E532EF3FC5F0020D282 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -2415,10 +2494,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReaderHive/Pods-ReaderHive-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
+ inputPaths = (
+ );
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReaderHive/Pods-ReaderHive-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
+ outputPaths = (
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReaderHive/Pods-ReaderHive-frameworks.sh\"\n";
@@ -2886,8 +2969,24 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 85859E512EF3FC5F0020D282 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 85859E662EF3FC6E0020D282 /* NotificationService.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ 85859E5B2EF3FC5F0020D282 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 85859E542EF3FC5F0020D282 /* NotificationService */;
+ targetProxy = 85859E5A2EF3FC5F0020D282 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin PBXVariantGroup section */
03980F812ED009EB0006E317 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
@@ -3120,6 +3219,68 @@
};
name = Release;
};
+ 85859E5E2EF3FC5F0020D282 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 9JR2Y32ZU3;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationService/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.lssj.ReaderHive.NotificationService;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 85859E5F2EF3FC5F0020D282 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 9JR2Y32ZU3;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationService/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.lssj.ReaderHive.NotificationService;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -3141,6 +3302,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 85859E612EF3FC5F0020D282 /* Build configuration list for PBXNativeTarget "NotificationService" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 85859E5E2EF3FC5F0020D282 /* Debug */,
+ 85859E5F2EF3FC5F0020D282 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
diff --git a/ReaderHive.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme b/ReaderHive.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme
new file mode 100644
index 0000000..84723fe
--- /dev/null
+++ b/ReaderHive.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReaderHive/Source/Info.plist b/ReaderHive/Source/Info.plist
index d5312a0..cb3b218 100644
--- a/ReaderHive/Source/Info.plist
+++ b/ReaderHive/Source/Info.plist
@@ -46,6 +46,10 @@
+ UIBackgroundModes
+
+ remote-notification
+
UIDesignRequiresCompatibility