diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index fba494e..9d08f9b 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -45,8 +45,8 @@ class NotificationService: UNNotificationServiceExtension { } private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws { - switch content.categoryIdentifier { - case "messaging.message", "messaging.callStart": + switch content.userInfo["type"] as? String { + case "messages.new": try handleMessagingNotification(request: request, content: content) default: try handleDefaultNotification(content: content) @@ -54,11 +54,11 @@ class NotificationService: UNNotificationServiceExtension { } 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 meta = content.userInfo["meta"] as? [AnyHashable: Any] else { + throw ParseNotificationPayloadError.missingMetadata("The notification has no meta.") } - guard let pfpIdentifier = metadata["pfp"] as? String else { + guard let pfpIdentifier = meta["pfp"] as? String else { throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no pfp.") } @@ -78,7 +78,7 @@ class NotificationService: UNNotificationServiceExtension { UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory]) content.categoryIdentifier = replyableMessageCategory.identifier - let metadataCopy = metadata as? [String: String] ?? [:] + let metaCopy = meta as? [String: String] ?? [:] let pfpUrl = getAttachmentUrl(for: pfpIdentifier) let targetSize = 512 @@ -93,17 +93,17 @@ class NotificationService: UNNotificationServiceExtension { print("Unable to get pfp url: \(error)") } - let handle = INPersonHandle(value: "\(metadataCopy["user_id"] ?? "")", type: .unknown) + let handle = INPersonHandle(value: "\(metaCopy["user_id"] ?? "")", type: .unknown) let sender = INPerson( personHandle: handle, - nameComponents: PersonNameComponents(nickname: "\(metadataCopy["sender_name"] ?? "")"), + nameComponents: PersonNameComponents(nickname: "\(metaCopy["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) + let intent = self.createMessageIntent(with: sender, meta: metaCopy, body: content.body) self.donateInteraction(for: intent) let updatedContent = try? request.content.updating(from: intent) self.contentHandler?(updatedContent ?? content) @@ -111,15 +111,15 @@ class NotificationService: UNNotificationServiceExtension { } private func handleDefaultNotification(content: UNMutableNotificationContent) throws { - guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else { - throw ParseNotificationPayloadError.missingMetadata("The notification has no metadata.") + guard let meta = content.userInfo["meta"] as? [AnyHashable: Any] else { + throw ParseNotificationPayloadError.missingMetadata("The notification has no meta.") } - if let imageIdentifier = metadata["image"] as? String { + if let imageIdentifier = meta["image"] as? String { attachMedia(to: content, withIdentifier: [imageIdentifier], fileType: UTType.webP, doScaleDown: true) - } else if let pfpIdentifier = metadata["pfp"] as? String { + } else if let pfpIdentifier = meta["pfp"] as? String { attachMedia(to: content, withIdentifier: [pfpIdentifier], fileType: UTType.webP, doScaleDown: true) - } else if let imagesIdentifier = metadata["images"] as? Array { + } else if let imagesIdentifier = meta["images"] as? Array { attachMedia(to: content, withIdentifier: imagesIdentifier, fileType: UTType.webP, doScaleDown: true) } else { contentHandler?(content) @@ -136,7 +136,7 @@ class NotificationService: UNNotificationServiceExtension { return } - let targetSize = 800 + let targetSize = 512 let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit) for attachmentUrl in attachmentUrls { @@ -202,13 +202,13 @@ class NotificationService: UNNotificationServiceExtension { self.contentHandler?(content) } - private func createMessageIntent(with sender: INPerson, metadata: [AnyHashable: Any], body: String) -> INSendMessageIntent { + private func createMessageIntent(with sender: INPerson, meta: [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"] ?? "")", + speakableGroupName: meta["room_name"] != nil ? INSpeakableString(spokenPhrase: meta["room_name"] as! String) : nil, + conversationIdentifier: "\(meta["room_id"] ?? "")", serviceName: nil, sender: sender, attachments: nil diff --git a/ios/Podfile b/ios/Podfile index 57f564f..c5ebe70 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -31,6 +31,8 @@ target 'Runner' do use_frameworks! use_modular_headers! + pod 'Alamofire' + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 26f683b..a404c0b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -344,6 +344,6 @@ SPEC CHECKSUMS: wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e -PODFILE CHECKSUM: c8120fa04387477e9e62f36b6c37495c69fca3bb +PODFILE CHECKSUM: f5ad824c068cb5da52a3e19417c4e0de970c1231 COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 770800c..37bf9d7 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 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, ); }; }; + 73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */; }; 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 */; }; @@ -78,6 +79,7 @@ 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 = ""; }; + 73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyDelegate.swift; 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -241,6 +243,7 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 73D4264A2DEB815D006C0AAE /* NotifyDelegate.swift */, ); path = Runner; sourceTree = ""; @@ -545,6 +548,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 73D4264B2DEB815D006C0AAE /* NotifyDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 1fe35f8..1f24ab4 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,11 +3,14 @@ import UIKit @main @objc class AppDelegate: FlutterAppDelegate { + let notifyDelegate = NotifyDelegate() + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) + UNUserNotificationCenter.current().delegate = notifyDelegate + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 35617c5..49bb2b5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion diff --git a/ios/Runner/NotifyDelegate.swift b/ios/Runner/NotifyDelegate.swift new file mode 100644 index 0000000..15b3432 --- /dev/null +++ b/ios/Runner/NotifyDelegate.swift @@ -0,0 +1,54 @@ +// +// NotifyDelegate.swift +// Runner +// +// Created by LittleSheep on 2025/6/1. +// + +import Foundation +import Alamofire + +class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate { + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + if let textResponse = response as? UNTextInputNotificationResponse { + let content = response.notification.request.content + guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else { + return + } + + var token: String = "" + if let tokenJson = UserDefaults.standard.string(forKey: "dyn_user_tk"), + let tokenData = tokenJson.data(using: String.Encoding.utf8), + let tokenDict = try? JSONSerialization.jsonObject(with: tokenData) as? [String: Any], + let tokenValue = tokenDict["token"] as? String { + token = tokenValue + } else { + return + } + + let serverUrl = "https://nt.solian.app" + let url = "\(serverUrl)/chat/\(metadata["room_id"])/messages" + + let parameters: [String: Any] = [ + "content": textResponse.userText, + "replied_message_id": metadata["message_id"] + ] + + AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders( + [HTTPHeader(name: "Authorization", value: "AtField \(token)")] + )) + .validate() + .responseString { response in + switch response.result { + case .success(_): + break + case .failure(let error): + print("Failed to send chat reply message: \(error)") + break + } + } + } + + completionHandler() + } +} diff --git a/ios/Runner/Services/CloudFile.swift b/ios/Runner/Services/CloudFile.swift index f50a3cc..e3985a2 100644 --- a/ios/Runner/Services/CloudFile.swift +++ b/ios/Runner/Services/CloudFile.swift @@ -8,7 +8,7 @@ import Foundation func getAttachmentUrl(for identifier: String) -> String { - let serverBaseUrl = "https://api.sn.solsynth.dev" + let serverBaseUrl = "https://nt.solian.app" - return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)" + return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/files/\(identifier)" } diff --git a/lib/widgets/chat/call_button.dart b/lib/widgets/chat/call_button.dart index ec53c64..b184422 100644 --- a/lib/widgets/chat/call_button.dart +++ b/lib/widgets/chat/call_button.dart @@ -14,6 +14,7 @@ part 'call_button.g.dart'; @riverpod Future ongoingCall(Ref ref, String roomId) async { + if (roomId.isEmpty) return null; try { final apiClient = ref.watch(apiClientProvider); final resp = await apiClient.get('/chat/realtime/$roomId'); diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 7cfa70c..aff0216 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/pubspec.yaml b/pubspec.yaml index 5c64907..6b7ab47 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+97 +version: 3.0.0+98 environment: sdk: ^3.7.2