如何使用flutter_local_notifications在IOS flutter中显示图像通知?

问题描述 投票:0回答:2

我正在向我的项目添加 FCM 图像通知支持,因此我正在监听来自 firebase 的消息并使用 flutter_image_notifications 进行显示。

对于 Android,它正在使用大图片 flutter_local_notifications 插件未在通知中显示大图片 我们该怎么做以及是否需要添加 Ios 通知服务扩展?如果是的话我需要快速程序

flutter firebase-cloud-messaging flutter-local-notification
2个回答
0
投票

flutter 本地通知适用于 iOS 和 Android 如果你配置你的项目

首先这样做

  1. https://firebase.google.com/docs/flutter/setup?platform=ios
  2. https://firebase.flutter.dev/docs/messaging/overview/
  3. https://firebase.google.com/docs/cloud-messaging/flutter/client

然后实现 https://pub.dev/packages/flutter_local_notifications

关于添加图像的部分描述得很糟糕,因此如果您想从网络下载图像,它是要粘贴的代码的主要部分, 重点是您需要在电话文件系统中提供存储图片的路径。

  final http.Response response = await http.get(Uri.parse(
"https:PICTURE_ADRESS_IN_THE_WEB.jpg"));

  // Get temporary directory
  final dir = await getTemporaryDirectory();

  // Create an image name
  var filename = '${dir.path}/image.png';

  // Save to filesystem
  final file = File(filename);
  await file.writeAsBytes(response.bodyBytes);


  DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
      attachments: [DarwinNotificationAttachment(filename)]);

0
投票

对于 IOS,您需要为您的项目创建一个NotificationExtension,以便能够处理多媒体(图像、视频...),好的一点是即使终止,它也能工作。

步骤如下(在 XCode 中打开 IOS 文件夹): 1-导航到文件 -> 新建 -> 目标 2-搜索“通知服务扩展” 3-确保选择跑步者作为目标,然后单击“完成” 4-单击扩展目标,然后转到BuildSettings->打包(产品包标识符)并确保包名称前缀与Runner包名称相同。 5- 转到“签名和功能”-> + 功能-> 应用程序组 -> 创建新的/或选择现有的 6- 通过选择您在步骤 5 中选择的相同应用程序组,为跑步者目标重复步骤 5

以下代码可以放在扩展 swift 文件中:

import UserNotifications

导入意图

类NotificationService:UNNotificationServiceExtension {

var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?

/// This is called when a notification is received.
/// - Parameters:
///   - request: The notification request.
///   - contentHandler: The callback that needs to be called when the notification is ready to be displayed.
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler


    // the OS already did some work for us so we do make a work copy. If something does not go the way we expect or we run in a timeout we still have an attempt that can be displayed.
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

    // unwrapping makes the compiler happy
    if(bestAttemptContent == nil) {
        return;
    }

    // this is the FCM / APNS payload defined by the server / caller.
    // Its the custom '"data"' object you provide in the FCM json.
    // adjust it to your needs. This is just an example.
    let payload: [AnyHashable : Any] = bestAttemptContent!.userInfo

    // we assume that we get a type in the payload
    // either remove this line or add a type to your payload
    let type: String? = payload["type"] as? String

    // this is set by the server to indicate that this is a chat message
    if(type == "chat") {
        _handleChatMessage(payload: payload)

        return;
    }

    
    // if we do not know the type we just pass the notification through
    // this is the case when we get a plain FCM / APNS notification
    guard let attachmentURL = payload["iosRichLink"] as? String else {
               contentHandler(request.content)
               return
           }
           
           do {
               let url = URL(string: attachmentURL)
               let fileName = url!.lastPathComponent // same here => we hope it contains a proper file extension.

               let imageData = try? Data(contentsOf: url!)

               if(imageData != nil) {
                   let attachment = createNotificationAttachment(identifier: "media", fileName: fileName, data: imageData!, options: nil)

                   if attachment != nil {
                       bestAttemptContent?.attachments.append(attachment!)
                   }
               }
               
               contentHandler(bestAttemptContent?.copy() as! UNNotificationContent)
               
           } 


    if let bestAttemptContent =  bestAttemptContent {
        contentHandler(bestAttemptContent)
    }
}

/// Handles a chat message notification. It tries to display it as a communication notification.
/// - Parameter payload: The FCM / APNS payload, defined by the server / caller.
func _handleChatMessage(payload: [AnyHashable : Any]) {
    guard let content = bestAttemptContent else {
        return
    }

    guard let contentHandler = contentHandler else {
        return
    }

    // add your custom logic here. Read the payload information and act accordingly.
    // all following code assumes you provide this information in the payload.

    // let chatRoomName: String? = payload["chatRoomName"] as? String

    // guard let chatRoomName: String = chatRoomName, !chatRoomName.isEmpty else {
    //     return
    // }
    

    let chatRoomName: String? = payload["chatRoomName"] as? String //"My Custom Room" // this can be a senders name or the name of a channel or group

    let senderId: String? = payload["senderId"] as? String //"f91840a2-a1bd-4d7a-a7ea-b4c08f7292e0" // use whatever value you have from your backend

    let senderDisplayName: String? = payload["senderDisplayName"] as? String  //"Sender A"
    
    let senderThumbnail: String = payload["senderThumbnail"] as! String //"https://picsum.photos/300"

    guard let senderThumbnailUrl: URL = URL(string: senderThumbnail) else {
        return
    }

    let senderThumbnailFileName: String = senderThumbnailUrl.lastPathComponent // we grab the last part in the hope it contains the actual filename (any-picture.jpg)

    guard let senderThumbnailImageData: Data = try? Data(contentsOf: senderThumbnailUrl),
        let senderThumbnailImageFileUrl: URL = try? downloadAttachment(data: senderThumbnailImageData, fileName: senderThumbnailFileName),
        let senderThumbnailImageFileData: Data = try? Data(contentsOf: senderThumbnailImageFileUrl) else {

        return
    }

    // example for adding attachments. Will be displayed by the communication notification.
    var attachments: [UNNotificationAttachment] = [];

    // Note: TODO -> Make sure that it has a file extension. Otherwise the creation of an attachment will fail and return nil.
    let link: String? = payload["iosRichLink"] as? String

    if let link = link, !link.isEmpty {
        let url = URL(string: link)
        let fileName = url!.lastPathComponent // same here => we hope it contains a proper file extension.

        let imageData = try? Data(contentsOf: url!)

        if(imageData != nil) {
            let attachment = createNotificationAttachment(identifier: "media", fileName: fileName, data: imageData!, options: nil)

            if attachment != nil {
                attachments.append(attachment!)
            }
        }
    }

    // Add a preview to the notification.
    // Maybe the sender attached a picture or a video.
    // Handle attachments here before converting it to a communication notification
    // as I had issues when trying adding attachments afterwards.
    // Note: Those can be reused in the Notification Content Extension
    content.attachments = attachments

    // profile picture that will be displayed in the notification (left side)
    let senderAvatar: INImage = INImage(imageData: senderThumbnailImageFileData)

    var personNameComponents = PersonNameComponents()
    personNameComponents.nickname = senderDisplayName

    // the person that sent the message
    // we need that as it is used by the OS trying to identify/match the sender with a contact
    // Setting ".unknown" as type will prevent the OS from trying to match the sender with a contact
    // as here this is an internal identifier and not a phone number or email
    let senderPerson = INPerson(
                                personHandle: INPersonHandle(
                                                value: senderId,
                                                type: .unknown
                                            ),
                                nameComponents: personNameComponents,
                                displayName: senderDisplayName,
                                image: senderAvatar,
                                contactIdentifier: nil,
                                customIdentifier: nil,
                                isMe: false, // this makes the OS recognize this as a sender
                                suggestionType: .none
                            )

    // this is just a dummy person that will be used as the recipient
    let selfPerson = INPerson(
                                personHandle: INPersonHandle(
                                                value: "00000000-0000-0000-0000-000000000000", // no need to set a real value here
                                                type: .unknown
                                            ),
                                nameComponents: nil,
                                displayName: nil,
                                image: nil,
                                contactIdentifier: nil,
                                customIdentifier: nil,
                                isMe: true, // this makes the OS recognize this as "US"
                                suggestionType: .none
                            )

    // the actual message. We use the OS to send us ourselves a message.
    let incomingMessagingIntent = INSendMessageIntent(
                                        recipients: [selfPerson],
                                        outgoingMessageType: .outgoingMessageText, // This marks the message as outgoing
                                        content: content.body, // this will replace the content.body
                                        speakableGroupName: nil,
                                        conversationIdentifier: chatRoomName, // this will be used as the conversation title
                                        serviceName: nil,
                                        sender: senderPerson, // this marks the message sender as the person we defined above
                                        attachments: []
                                )

    incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender)

    let interaction = INInteraction(intent: incomingMessagingIntent, response: nil)

    interaction.direction = .incoming

    do {
        // we now update / patch / convert our attempt to a communication notification.
        bestAttemptContent = try content.updating(from: incomingMessagingIntent) as? UNMutableNotificationContent

        // everything went alright, we are ready to display our notification.
        contentHandler(bestAttemptContent!)
    } catch let error {
        print("error \(error)")
    }
}

/// 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.
override func serviceExtensionTimeWillExpire() {
    if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
        contentHandler(bestAttemptContent)
    }
}

/// Shorthand for creating a notification attachment.
/// - Parameters:
///   - identifier: Unique identifier for the attachment. So it can be referenced within a Notification Content extension for example.
///   - fileName: The name of the file. This is the name that will be used to store the name on disk.
///   - data: A Data object based on the remote url.
///   - options: A dictionary of options. See Apple's documentation for more information.
/// - Returns: A UNNotificationAttachment object.
func createNotificationAttachment(identifier: String, fileName: String, data: Data, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
    do {
       if let fileURL: URL = downloadAttachment(data: data, fileName: fileName) {
            let attachment: UNNotificationAttachment = try UNNotificationAttachment.init(identifier: identifier, url: fileURL, options: options)

            return attachment
        }

        return nil
    } catch let error {
        print("error \(error)")
    }

    return nil
}

/// Downloads a file from a remote url and stores it in a temporary folder.
/// - Parameters:
///   - data: A Data object based on the remote url.
///   - fileName: The name of the file. This is the name that will be used to store the name on disk.
/// - Returns: A URL object pointing to the temporary file on the phone. This can be used by a Notification Content extension for example.
func downloadAttachment(data: Data, fileName: String) -> URL? {
    // Create a temporary file URL to write the file data to
    let fileManager = FileManager.default
    let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
    let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)

    do {
        // prepare temp subfolder
        try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
        let fileURL: URL = tmpSubFolderURL.appendingPathComponent(fileName)

        // Save the image data to the local file URL
        try data.write(to: fileURL)

        return fileURL
    } catch let error {
        print("error \(error)")
    }

    return nil
}

}

此代码处理图像和通信头像图像,下面是我用于此代码的 json 结构:

{
// "to": "",

// The value should be an array of registration tokens to which to send the multicast message. The array must contain at least 1 and at most 1000 registration tokens. To send a message to a single device, use the to parameter.
"registration_ids":[""],
"collapseKey": "com.mondee.abhi.uat",
"contentAvailable": true,
"data": {
    //IOS Image and Media and Multiple Icons -> "type": "chat"
    "type": "chat",
    "displayInApp": true,
    "inAppType":"connectingFlightTimer", //Flight delayed, flight added to trip
    //based on "inAppType" value, will be determined
    "content":{
        "images":["img1","img2"],
        "remainingTime" : "3.3",
        "airlinesLogo":"https://image-url.com"
    
    },
    "channelName": "High Importance Notifications",
    "channelDescription": "This channel is used for important notifications.",
    ///////////////////////IOS//////////////////////
    //IOS Image and Media and Multiple Icons      
    // "type": "chat",
    //"My Custom Room" // this can be a senders name or the name of a channel or group
    "chatRoomName": "any name",
    // use whatever value you have from your backend
    "senderId": "f91840a2-a1bd-4d7a-a7ea-b4c08f7292e0",
    "senderThumbnail": "https://picsum.photos/300",
    "senderDisplayName": "Bara Batta",
    //https://fastly.picsum.photos/id/368/536/354.jpg?hmac=2b0UU6Y-8XxkiRBhatgBJ-ni3aWJ5CcVVENpX-mEiIA
    "iosRichLink": "https://downloads.intercomcdn.com/i/o/149067330/c8067812d60c979598f4b225/PushVideo.gif",
    ///////////////////////END IOS////////////////////

    //use always
    "click_action": "FLUTTER_NOTIFICATION_CLICK",
    //if we need to show avatar for android - flutter local notification
    "avatar": "https://picsum.photos/30/30"
},
  //it "type" chat ,sender user id
"from": "201322439669",
//it "type" chat 
"messageId": "0:1711565124543896%6b4165a56b4165a5",
"mutableContent": true,
// This parameter specifies how long (in seconds) the message should be kept in FCM storage if the device is offline. The maximum time to live supported is 4 weeks, and the default value is 4 weeks.
"time_to_live":2419200,
"notification": {
    //specify the channel, so diferrent icon and diferrent group of noifications will be handeled
    "channelId": "high_importance_channel",//normal channel
    //for BigPicture notification, Android
    "image": "https://images.trippro.com/deals/destination/300/SFO.png",
    //for IOS and Android - if type is "chat" then SenderName will be the title 
    "title": "Flight Alert",
    //for localization 
    "titleLocArgs": [],
    "titleLocKey": null,

    //for IOS and Android
    "body": "We regret to inform you that your Flight {Flight Number} scheduled to depart on {date} from {Origin code} to {Destination code} has been cancelled by the airline. We apologize for the inconvenience caused.",
    //for localization 
    "bodyLocArgs": [],
    "bodyLocKey": null,
    "android": {
        "channelId": "high_importance_channel",
        "clickAction": null,
        "color": "primaryColor",
        "count": null,
        // "imageUrl": "https://images.trippro.com/deals/destination/300/SFO.png",
        "link": null,
        "priority": "high",
        "smallIcon": "abhi_notify_general",
        "sound": "default",
        "ticker": null,
        "tag": "campaign_collapse_key_9125358200005252361",
        "visibility": "public"
    },
    "web": null
},
//extra params we can pass to IOS
"acme1": "bar",
"acme2": [
    "bang",
    "whiz"
],
"sentTime": 1711565124534,
"threadId": null,
"ttl": 86400

}

要使用邮递员发送通知,您可以使用旧方法(目前有效):

端点:

https://fcm.googleapis.com/fcm/send

在标题中:授权:key=

© www.soinside.com 2019 - 2024. All rights reserved.