diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist
new file mode 100644
index 0000000..ad04f44
--- /dev/null
+++ b/ios/NotificationService/Info.plist
@@ -0,0 +1,17 @@
+
+
+
+
+ NSUserActivityTypes
+
+ INSendMessageIntent
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.usernotifications.service
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).NotificationService
+
+
+
diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift
new file mode 100644
index 0000000..fba494e
--- /dev/null
+++ b/ios/NotificationService/NotificationService.swift
@@ -0,0 +1,223 @@
+//
+// NotificationService.swift
+// NotificationService
+//
+// Created by LittleSheep on 2025/5/31.
+//
+
+import UserNotifications
+import Intents
+import Kingfisher
+import UniformTypeIdentifiers
+
+enum ParseNotificationPayloadError: Error {
+ case missingMetadata(String)
+ case missingAvatarUrl(String)
+}
+
+class NotificationService: UNNotificationServiceExtension {
+
+ private var contentHandler: ((UNNotificationContent) -> Void)?
+ private var bestAttemptContent: UNMutableNotificationContent?
+
+ override func didReceive(
+ _ request: UNNotificationRequest,
+ withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
+ ) {
+ self.contentHandler = contentHandler
+ guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
+ contentHandler(request.content)
+ return
+ }
+ self.bestAttemptContent = bestAttemptContent
+
+ do {
+ try processNotification(request: request, content: bestAttemptContent)
+ } catch {
+ contentHandler(bestAttemptContent)
+ }
+ }
+
+ override func serviceExtensionTimeWillExpire() {
+ if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
+ contentHandler(bestAttemptContent)
+ }
+ }
+
+ private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
+ switch content.categoryIdentifier {
+ case "messaging.message", "messaging.callStart":
+ try handleMessagingNotification(request: request, content: content)
+ default:
+ try handleDefaultNotification(content: content)
+ }
+ }
+
+ private func handleMessagingNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
+ guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
+ throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.")
+ }
+
+ guard let pfpIdentifier = metadata["pfp"] as? String else {
+ throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no pfp.")
+ }
+
+ let replyableMessageCategory = UNNotificationCategory(
+ identifier: content.categoryIdentifier,
+ actions: [
+ UNTextInputNotificationAction(
+ identifier: "reply_action",
+ title: "Reply",
+ options: []
+ ),
+ ],
+ intentIdentifiers: [],
+ options: []
+ )
+
+ UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
+ content.categoryIdentifier = replyableMessageCategory.identifier
+
+ let metadataCopy = metadata as? [String: String] ?? [:]
+ let pfpUrl = getAttachmentUrl(for: pfpIdentifier)
+
+ let targetSize = 512
+ let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
+
+ KingfisherManager.shared.retrieveImage(with: URL(string: pfpUrl)!, options: [.processor(scaleProcessor)], completionHandler: { result in
+ var image: Data?
+ switch result {
+ case .success(let value):
+ image = value.image.pngData()
+ case .failure(let error):
+ print("Unable to get pfp url: \(error)")
+ }
+
+ let handle = INPersonHandle(value: "\(metadataCopy["user_id"] ?? "")", type: .unknown)
+ let sender = INPerson(
+ personHandle: handle,
+ nameComponents: PersonNameComponents(nickname: "\(metadataCopy["sender_name"] ?? "")"),
+ displayName: content.title,
+ image: image == nil ? nil : INImage(imageData: image!),
+ contactIdentifier: nil,
+ customIdentifier: nil
+ )
+
+ let intent = self.createMessageIntent(with: sender, metadata: metadataCopy, body: content.body)
+ self.donateInteraction(for: intent)
+ let updatedContent = try? request.content.updating(from: intent)
+ self.contentHandler?(updatedContent ?? content)
+ })
+ }
+
+ private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
+ guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
+ throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.")
+ }
+
+ if let imageIdentifier = metadata["image"] as? String {
+ attachMedia(to: content, withIdentifier: [imageIdentifier], fileType: UTType.webP, doScaleDown: true)
+ } else if let pfpIdentifier = metadata["pfp"] as? String {
+ attachMedia(to: content, withIdentifier: [pfpIdentifier], fileType: UTType.webP, doScaleDown: true)
+ } else if let imagesIdentifier = metadata["images"] as? Array {
+ attachMedia(to: content, withIdentifier: imagesIdentifier, fileType: UTType.webP, doScaleDown: true)
+ } else {
+ contentHandler?(content)
+ }
+ }
+
+ private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: Array, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
+ let attachmentUrls = identifier.compactMap { element in
+ return getAttachmentUrl(for: element)
+ }
+
+ guard !attachmentUrls.isEmpty else {
+ print("Invalid URLs for attachments: \(attachmentUrls)")
+ return
+ }
+
+ let targetSize = 800
+ let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
+
+ for attachmentUrl in attachmentUrls {
+ guard let remoteUrl = URL(string: attachmentUrl) else {
+ print("Invalid URL for attachment: \(attachmentUrl)")
+ continue // Skip this URL and move to the next one
+ }
+
+ KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
+ .processor(scaleProcessor)
+ ] : nil) { [weak self] result in
+ guard let self = self else { return }
+
+ switch result {
+ case .success(let retrievalResult):
+ // The image is either retrieved from cache or downloaded
+ let tempDirectory = FileManager.default.temporaryDirectory
+ let cachedFileUrl = tempDirectory.appendingPathComponent(UUID().uuidString) // Unique identifier for each file
+
+ do {
+ // Write the image data to a temporary file for UNNotificationAttachment
+ try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
+ self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: attachmentUrl)
+ } catch {
+ print("Failed to write media to temporary file: \(error.localizedDescription)")
+ self.contentHandler?(content)
+ }
+
+ case .failure(let error):
+ print("Failed to retrieve image: \(error.localizedDescription)")
+ self.contentHandler?(content)
+ }
+ }
+ }
+ }
+
+ private func attachLocalMedia(to content: UNMutableNotificationContent, fileType type: String?, from localUrl: URL, withIdentifier identifier: String) {
+ do {
+ let attachment = try UNNotificationAttachment(identifier: identifier, url: localUrl, options: [
+ UNNotificationAttachmentOptionsTypeHintKey: type as Any,
+ UNNotificationAttachmentOptionsThumbnailHiddenKey: 0,
+ ])
+ content.attachments = [attachment]
+ } catch let error as NSError {
+ // Log detailed error information
+ print("Failed to create attachment from file at \(localUrl.path)")
+ print("Error: \(error.localizedDescription)")
+
+ // Check specific error codes if needed
+ if error.domain == NSCocoaErrorDomain {
+ switch error.code {
+ case NSFileReadNoSuchFileError:
+ print("File does not exist at \(localUrl.path)")
+ case NSFileReadNoPermissionError:
+ print("No permission to read file at \(localUrl.path)")
+ default:
+ print("Unhandled file error: \(error.code)")
+ }
+ }
+ }
+
+ // Call content handler regardless of success or failure
+ self.contentHandler?(content)
+ }
+
+ private func createMessageIntent(with sender: INPerson, metadata: [AnyHashable: Any], body: String) -> INSendMessageIntent {
+ INSendMessageIntent(
+ recipients: nil,
+ outgoingMessageType: .outgoingMessageText,
+ content: body,
+ speakableGroupName: metadata["room_name"] != nil ? INSpeakableString(spokenPhrase: metadata["room_name"] as! String) : nil,
+ conversationIdentifier: "\(metadata["room_id"] ?? "")",
+ serviceName: nil,
+ sender: sender,
+ attachments: nil
+ )
+ }
+
+ private func donateInteraction(for intent: INIntent) {
+ let interaction = INInteraction(intent: intent, response: nil)
+ interaction.direction = .incoming
+ interaction.donate(completion: nil)
+ }
+}
diff --git a/ios/Podfile b/ios/Podfile
index 4929876..57f564f 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -29,13 +29,19 @@ flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
-
- pod 'Kingfisher', '~> 8.0'
+ use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+
target 'RunnerTests' do
inherit! :search_paths
end
+
+ target 'NotificationService' do
+ inherit! :search_paths
+ pod 'Kingfisher', '~> 8.0'
+ pod 'Alamofire'
+ end
end
post_install do |installer|
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 823f4a2..26f683b 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,4 +1,5 @@
PODS:
+ - Alamofire (5.10.2)
- connectivity_plus (0.0.1):
- Flutter
- croppy (0.0.1):
@@ -187,6 +188,7 @@ PODS:
- WebRTC-SDK (125.6422.07)
DEPENDENCIES:
+ - Alamofire
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- croppy (from `.symlinks/plugins/croppy/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
@@ -218,6 +220,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
+ - Alamofire
- DKImagePickerController
- DKPhotoGallery
- Firebase
@@ -294,6 +297,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
+ Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
@@ -340,6 +344,6 @@ SPEC CHECKSUMS:
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e
-PODFILE CHECKSUM: 2608312fddeea6d787ca3aa478c756da8f4ca66d
+PODFILE CHECKSUM: c8120fa04387477e9e62f36b6c37495c69fca3bb
COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index aae37a6..770800c 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -10,8 +10,10 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 73268D1C2DEAFD670076E970 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73268D152DEAFD670076E970 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
755557017FD1B99AFC4F9127 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */; };
+ 8529D00678947B00A0162116 /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -27,9 +29,27 @@
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
+ 73268D1A2DEAFD670076E970 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 73268D142DEAFD670076E970;
+ remoteInfo = NotificationService;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
+ 73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ 73268D1C2DEAFD670076E970 /* NotificationService.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -47,12 +67,16 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ 192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = ""; };
1C14F71D23E4371602065522 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = ""; };
29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3A1C47BD29CC6AC2587D4DBE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 73268D152DEAFD670076E970 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
@@ -66,10 +90,47 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; };
A499FDB2082EB000933AA8C5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
F6D834CA86410B09796B312B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 73268D212DEAFD670076E970 /* Exceptions for "NotificationService" folder in "NotificationService" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = 73268D142DEAFD670076E970 /* NotificationService */;
+ };
+ 73268D2B2DEB013D0076E970 /* Exceptions for "Services" folder in "NotificationService" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ CloudFile.swift,
+ );
+ target = 73268D142DEAFD670076E970 /* NotificationService */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 73268D162DEAFD670076E970 /* NotificationService */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 73268D212DEAFD670076E970 /* Exceptions for "NotificationService" folder in "NotificationService" target */,
+ );
+ path = NotificationService;
+ sourceTree = "";
+ };
+ 73268D272DEB012A0076E970 /* Services */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 73268D2B2DEB013D0076E970 /* Exceptions for "Services" folder in "NotificationService" target */,
+ );
+ path = Services;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
/* Begin PBXFrameworksBuildPhase section */
1DFF8FEBBD0CF5A990600776 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -79,6 +140,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 73268D122DEAFD670076E970 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8529D00678947B00A0162116 /* Pods_NotificationService.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -103,6 +172,7 @@
children = (
F6D834CA86410B09796B312B /* Pods_Runner.framework */,
29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */,
+ AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -116,6 +186,9 @@
14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */,
14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */,
E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */,
+ 192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */,
+ 252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */,
+ 2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -136,6 +209,7 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
+ 73268D162DEAFD670076E970 /* NotificationService */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
91E124CE95BCB4DCD890160D /* Pods */,
@@ -149,6 +223,7 @@
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ 73268D152DEAFD670076E970 /* NotificationService.appex */,
);
name = Products;
sourceTree = "";
@@ -156,6 +231,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ 73268D272DEB012A0076E970 /* Services */,
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
@@ -191,6 +267,27 @@
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ 73268D142DEAFD670076E970 /* NotificationService */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 73268D222DEAFD670076E970 /* Build configuration list for PBXNativeTarget "NotificationService" */;
+ buildPhases = (
+ 042F7A1E06D2751BE114E578 /* [CP] Check Pods Manifest.lock */,
+ 73268D112DEAFD670076E970 /* Sources */,
+ 73268D122DEAFD670076E970 /* Frameworks */,
+ 73268D132DEAFD670076E970 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 73268D162DEAFD670076E970 /* NotificationService */,
+ );
+ name = NotificationService;
+ productName = NotificationService;
+ productReference = 73268D152DEAFD670076E970 /* NotificationService.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
@@ -199,6 +296,7 @@
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
+ 73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
@@ -208,6 +306,10 @@
buildRules = (
);
dependencies = (
+ 73268D1B2DEAFD670076E970 /* PBXTargetDependency */,
+ );
+ fileSystemSynchronizedGroups = (
+ 73268D272DEB012A0076E970 /* Services */,
);
name = Runner;
productName = Runner;
@@ -221,6 +323,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
+ LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
@@ -228,6 +331,9 @@
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
+ 73268D142DEAFD670076E970 = {
+ CreatedOnToolsVersion = 16.4;
+ };
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
@@ -235,7 +341,6 @@
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -243,12 +348,14 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ preferredProjectObjectVersion = 77;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
+ 73268D142DEAFD670076E970 /* NotificationService */,
);
};
/* End PBXProject section */
@@ -261,6 +368,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 73268D132DEAFD670076E970 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -276,6 +390,28 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 042F7A1E06D2751BE114E578 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-NotificationService-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -396,6 +532,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 73268D112DEAFD670076E970 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -413,6 +556,11 @@
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
+ 73268D1B2DEAFD670076E970 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 73268D142DEAFD670076E970 /* NotificationService */;
+ targetProxy = 73268D1A2DEAFD670076E970 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -562,6 +710,123 @@
};
name = Profile;
};
+ 73268D1E2DEAFD670076E970 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = W7HPZ53V6B;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationService/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.NotificationService;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 73268D1F2DEAFD670076E970 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = W7HPZ53V6B;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationService/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.NotificationService;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 73268D202DEAFD670076E970 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = W7HPZ53V6B;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationService/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.NotificationService;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Profile;
+ };
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -737,6 +1002,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 73268D222DEAFD670076E970 /* Build configuration list for PBXNativeTarget "NotificationService" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 73268D1E2DEAFD670076E970 /* Debug */,
+ 73268D1F2DEAFD670076E970 /* Release */,
+ 73268D202DEAFD670076E970 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 903def2..29326e3 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -4,5 +4,7 @@
aps-environment
development
+ com.apple.developer.usernotifications.communication
+
diff --git a/ios/Runner/Services/CloudFile.swift b/ios/Runner/Services/CloudFile.swift
new file mode 100644
index 0000000..f50a3cc
--- /dev/null
+++ b/ios/Runner/Services/CloudFile.swift
@@ -0,0 +1,14 @@
+//
+// CloudFile.swift
+// Runner
+//
+// Created by LittleSheep on 2025/5/31.
+//
+
+import Foundation
+
+func getAttachmentUrl(for identifier: String) -> String {
+ let serverBaseUrl = "https://api.sn.solsynth.dev"
+
+ return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
+}
diff --git a/lib/screens/auth/tabs.dart b/lib/screens/auth/tabs.dart
index 566da58..2de5dfd 100644
--- a/lib/screens/auth/tabs.dart
+++ b/lib/screens/auth/tabs.dart
@@ -45,7 +45,6 @@ class TabsNavigationWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final useHorizontalLayout = isWideScreen(context);
- final useExpandableLayout = isWidestScreen(context);
final currentRoute = ref.watch(currentRouteProvider);
final notificationUnreadCount = ref.watch(
@@ -112,8 +111,6 @@ class TabsNavigationWidget extends HookConsumerWidget {
Gap(MediaQuery.of(context).padding.top + 8),
Expanded(
child: NavigationRail(
- minExtendedWidth: 200,
- extended: useExpandableLayout,
selectedIndex: activeIndex,
onDestinationSelected: (index) {
router.replace(routes[index]);
diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart
index 3a180aa..be77fb2 100644
--- a/lib/screens/chat/chat.dart
+++ b/lib/screens/chat/chat.dart
@@ -147,6 +147,7 @@ class ChatRoomListTile extends HookConsumerWidget {
: room.name ?? '',
),
subtitle: buildSubtitle(),
+ trailing: trailing, // Add this line
onTap: () async {
// Clear unread count if there are unread messages
ref.read(chatSummaryProvider.future).then((summary) {
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
index 0eec43d..1fa6dbe 100644
--- a/lib/screens/settings.dart
+++ b/lib/screens/settings.dart
@@ -147,6 +147,7 @@ class SettingsScreen extends HookConsumerWidget {
title: Text('settingsColorScheme').tr(),
content: SingleChildScrollView(
child: ColorPicker(
+ enableAlpha: false,
pickerColor: selectedColor,
onColorChanged: (color) {
selectedColor = color;
diff --git a/lib/widgets/content/image.dart b/lib/widgets/content/image.dart
index 33291e3..524d2fc 100644
--- a/lib/widgets/content/image.dart
+++ b/lib/widgets/content/image.dart
@@ -1 +1,53 @@
-export 'image.native.dart' if (dart.library.html) 'image.web.dart';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_blurhash/flutter_blurhash.dart';
+
+class UniversalImage extends StatelessWidget {
+ final String uri;
+ final String? blurHash;
+ final BoxFit fit;
+ final double? width;
+ final double? height;
+ final bool noCacheOptimization;
+
+ const UniversalImage({
+ super.key,
+ required this.uri,
+ this.blurHash,
+ this.fit = BoxFit.cover,
+ this.width,
+ this.height,
+ this.noCacheOptimization = false,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ int? cacheWidth;
+ int? cacheHeight;
+ if (width != null && height != null && !noCacheOptimization) {
+ final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
+ cacheWidth = width != null ? (width! * devicePixelRatio).round() : null;
+ cacheHeight =
+ height != null ? (height! * devicePixelRatio).round() : null;
+ }
+
+ return SizedBox(
+ width: width,
+ height: height,
+ child: Stack(
+ fit: StackFit.expand,
+ children: [
+ if (blurHash != null) BlurHash(hash: blurHash!),
+ CachedNetworkImage(
+ imageUrl: uri,
+ fit: fit,
+ width: width,
+ height: height,
+ memCacheHeight: cacheHeight,
+ memCacheWidth: cacheWidth,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/content/image.native.dart b/lib/widgets/content/image.native.dart
deleted file mode 100644
index 524d2fc..0000000
--- a/lib/widgets/content/image.native.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-import 'package:cached_network_image/cached_network_image.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_blurhash/flutter_blurhash.dart';
-
-class UniversalImage extends StatelessWidget {
- final String uri;
- final String? blurHash;
- final BoxFit fit;
- final double? width;
- final double? height;
- final bool noCacheOptimization;
-
- const UniversalImage({
- super.key,
- required this.uri,
- this.blurHash,
- this.fit = BoxFit.cover,
- this.width,
- this.height,
- this.noCacheOptimization = false,
- });
-
- @override
- Widget build(BuildContext context) {
- int? cacheWidth;
- int? cacheHeight;
- if (width != null && height != null && !noCacheOptimization) {
- final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
- cacheWidth = width != null ? (width! * devicePixelRatio).round() : null;
- cacheHeight =
- height != null ? (height! * devicePixelRatio).round() : null;
- }
-
- return SizedBox(
- width: width,
- height: height,
- child: Stack(
- fit: StackFit.expand,
- children: [
- if (blurHash != null) BlurHash(hash: blurHash!),
- CachedNetworkImage(
- imageUrl: uri,
- fit: fit,
- width: width,
- height: height,
- memCacheHeight: cacheHeight,
- memCacheWidth: cacheWidth,
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/widgets/content/image.web.dart b/lib/widgets/content/image.web.dart
deleted file mode 100644
index 9c355b9..0000000
--- a/lib/widgets/content/image.web.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:web/web.dart' as web;
-import 'package:flutter/material.dart';
-
-class UniversalImage extends StatelessWidget {
- final String uri;
- final String? blurHash;
- final BoxFit fit;
- final double? width;
- final double? height;
- // No cache optimization for web
- final bool noCacheOptimization;
-
- const UniversalImage({
- super.key,
- required this.uri,
- this.blurHash,
- this.fit = BoxFit.cover,
- this.width,
- this.height,
- this.noCacheOptimization = false,
- });
-
- @override
- Widget build(BuildContext context) {
- return HtmlElementView.fromTagName(
- tagName: 'img',
- onElementCreated: (element) {
- element as web.HTMLImageElement;
- element.src = uri;
- element.style.width = width?.toString() ?? '100%';
- element.style.height = height?.toString() ?? '100%';
- element.style.objectFit = switch (fit) {
- BoxFit.cover || BoxFit.fitWidth || BoxFit.fitHeight => 'cover',
- BoxFit.fill => 'fill',
- BoxFit.contain => 'contain',
- BoxFit.none => 'none',
- _ => 'cover',
- };
- },
- );
- }
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index d1cdb8e..5c64907 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
-version: 3.0.0+96
+version: 3.0.0+97
environment:
sdk: ^3.7.2