Skip to content

Commit

Permalink
Implement options to hide or crop attachments in the thumbnail on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
noinskit committed Nov 24, 2022
1 parent 1d8b9d8 commit cc0373b
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 29 deletions.
56 changes: 52 additions & 4 deletions flutter_local_notifications/example/lib/main.dart
Expand Up @@ -826,9 +826,26 @@ class _HomePageState extends State<HomePage> {
},
),
PaddedElevatedButton(
buttonText: 'Show notification with attachment',
buttonText:
'Show notification with attachment (with thumbnail)',
onPressed: () async {
await _showNotificationWithAttachment();
await _showNotificationWithAttachment(
hideThumbnail: false);
},
),
PaddedElevatedButton(
buttonText:
'Show notification with attachment (no thumbnail)',
onPressed: () async {
await _showNotificationWithAttachment(
hideThumbnail: true);
},
),
PaddedElevatedButton(
buttonText:
'Show notification with attachment (clipped thumbnail)',
onPressed: () async {
await _showNotificationWithClippedThumbnailAttachment();
},
),
PaddedElevatedButton(
Expand Down Expand Up @@ -2162,12 +2179,43 @@ class _HomePageState extends State<HomePage> {
payload: 'item x');
}

Future<void> _showNotificationWithAttachment() async {
Future<void> _showNotificationWithAttachment({
required bool hideThumbnail,
}) async {
final String bigPicturePath = await _downloadAndSaveFile(
'https://via.placeholder.com/600x200', 'bigPicture.jpg');
final DarwinNotificationDetails darwinNotificationDetails =
DarwinNotificationDetails(attachments: <DarwinNotificationAttachment>[
DarwinNotificationAttachment(
bigPicturePath,
hideThumbnail: hideThumbnail,
)
]);
final NotificationDetails notificationDetails = NotificationDetails(
iOS: darwinNotificationDetails, macOS: darwinNotificationDetails);
await flutterLocalNotificationsPlugin.show(
id++,
'notification with attachment title',
'notification with attachment body',
notificationDetails);
}

Future<void> _showNotificationWithClippedThumbnailAttachment() async {
final String bigPicturePath = await _downloadAndSaveFile(
'https://via.placeholder.com/600x200', 'bigPicture.jpg');
final DarwinNotificationDetails darwinNotificationDetails =
DarwinNotificationDetails(attachments: <DarwinNotificationAttachment>[
DarwinNotificationAttachment(bigPicturePath)
DarwinNotificationAttachment(
bigPicturePath,
thumbnailClippingRect:
// lower right quadrant of the attachment
const DarwinNotificationAttachmentThumbnailClippingRect(
x: 0.5,
y: 0.5,
height: 0.5,
width: 0.5,
),
)
]);
final NotificationDetails notificationDetails = NotificationDetails(
iOS: darwinNotificationDetails, macOS: darwinNotificationDetails);
Expand Down
Expand Up @@ -67,6 +67,8 @@ @implementation FlutterLocalNotificationsPlugin {
NSString *const ATTACHMENTS = @"attachments";
NSString *const ATTACHMENT_IDENTIFIER = @"identifier";
NSString *const ATTACHMENT_FILE_PATH = @"filePath";
NSString *const ATTACHMENT_HIDE_THUMBNAIL = @"hideThumbnail";
NSString *const ATTACHMENT_THUMBNAIL_CLIPPING_RECT = @"thumbnailClippingRect";
NSString *const INTERRUPTION_LEVEL = @"interruptionLevel";
NSString *const THREAD_IDENTIFIER = @"threadIdentifier";
NSString *const PRESENT_ALERT = @"presentAlert";
Expand Down Expand Up @@ -912,14 +914,40 @@ - (void)cancelAll:(FlutterResult _Nonnull)result {
[NSMutableArray arrayWithCapacity:attachments.count];
for (NSDictionary *attachment in attachments) {
NSError *error;

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
if ([self containsKey:ATTACHMENT_HIDE_THUMBNAIL
forDictionary:attachment]) {
NSNumber *hideThumbnail = attachment[ATTACHMENT_HIDE_THUMBNAIL];
[options
setObject:hideThumbnail
forKey:UNNotificationAttachmentOptionsThumbnailHiddenKey];
}
if ([self containsKey:ATTACHMENT_THUMBNAIL_CLIPPING_RECT
forDictionary:attachment]) {
NSDictionary *thumbnailClippingRect =
attachment[ATTACHMENT_THUMBNAIL_CLIPPING_RECT];
CGRect rect =
CGRectMake([thumbnailClippingRect[@"x"] doubleValue],
[thumbnailClippingRect[@"y"] doubleValue],
[thumbnailClippingRect[@"width"] doubleValue],
[thumbnailClippingRect[@"height"] doubleValue]);
NSDictionary *rectDict =
CFBridgingRelease(CGRectCreateDictionaryRepresentation(rect));
[options
setObject:rectDict
forKey:
UNNotificationAttachmentOptionsThumbnailClippingRectKey];
}

UNNotificationAttachment *notificationAttachment =
[UNNotificationAttachment
attachmentWithIdentifier:attachment[ATTACHMENT_IDENTIFIER]
URL:[NSURL
fileURLWithPath:
attachment
[ATTACHMENT_FILE_PATH]]
options:nil
options:options
error:&error];
if (error) {
result(getFlutterError(error));
Expand Down
Expand Up @@ -48,10 +48,21 @@ extension DarwinInitializationSettingsMapper on DarwinInitializationSettings {
};
}

extension DarwinNotificationAttachmentMapper on DarwinNotificationAttachment {
extension on DarwinNotificationAttachmentThumbnailClippingRect {
Map<String, Object> toMap() => <String, Object>{
'x': x,
'y': y,
'width': width,
'height': height,
};
}

extension DarwinNotificationAttachmentMapper on DarwinNotificationAttachment {
Map<String, Object?> toMap() => <String, Object?>{
'identifier': identifier ?? '',
'filePath': filePath,
'hideThumbnail': hideThumbnail,
'thumbnailClippingRect': thumbnailClippingRect?.toMap(),
};
}

Expand Down
Expand Up @@ -2,9 +2,10 @@
/// systems such as iOS and macOS
class DarwinNotificationAttachment {
/// Constructs an instance of [DarwinNotificationAttachment].
const DarwinNotificationAttachment(
this.filePath, {
const DarwinNotificationAttachment(this.filePath, {
this.identifier,
this.hideThumbnail,
this.thumbnailClippingRect,
});

/// The local file path to the attachment.
Expand All @@ -18,4 +19,26 @@ class DarwinNotificationAttachment {
/// When left empty, the platform's native APIs will generate a unique
/// identifier
final String? identifier;

/// Should the attachment be considered for the notification thumbnail?
final bool? hideThumbnail;

/// The clipping rectangle for the thumbnail image.
final DarwinNotificationAttachmentThumbnailClippingRect? thumbnailClippingRect;
}

/// Represents the clipping rectangle used for the thumbnail image
class DarwinNotificationAttachmentThumbnailClippingRect {
const DarwinNotificationAttachmentThumbnailClippingRect({
required this.x,
required this.y,
required this.width,
required this.height,
});

final double x;
final double y;

final double width;
final double height;
}
Expand Up @@ -35,6 +35,8 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot
static let attachments = "attachments"
static let identifier = "identifier"
static let filePath = "filePath"
static let hideThumbnail = "hideThumbnail";
static let attachmentThumbnailClippingRect = "thumbnailClippingRect";
static let threadIdentifier = "threadIdentifier"
static let interruptionLevel = "interruptionLevel"
static let actionId = "actionId"
Expand Down Expand Up @@ -517,7 +519,24 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot
for attachment in attachments {
let identifier = attachment[MethodCallArguments.identifier] as! String
let filePath = attachment[MethodCallArguments.filePath] as! String
let notificationAttachment = try UNNotificationAttachment.init(identifier: identifier, url: URL.init(fileURLWithPath: filePath))
var options: [String: Any] = [:]
if let hideThumbnail = attachment[MethodCallArguments.hideThumbnail] as? NSNumber {
options[UNNotificationAttachmentOptionsThumbnailHiddenKey] = hideThumbnail
}
if let thumbnailClippingRect = attachment[MethodCallArguments.attachmentThumbnailClippingRect] as? [String: Any] {
let rect = CGRectMake(
thumbnailClippingRect["x"] as! Double,
thumbnailClippingRect["y"] as! Double,
thumbnailClippingRect["width"] as! Double,
thumbnailClippingRect["height"] as! Double
)
options[UNNotificationAttachmentOptionsThumbnailClippingRectKey] = rect.dictionaryRepresentation
}
let notificationAttachment = try UNNotificationAttachment.init(
identifier: identifier,
url: URL.init(fileURLWithPath: filePath),
options: options
)
content.attachments.append(notificationAttachment)
}
}
Expand Down
Expand Up @@ -242,10 +242,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': 'category1',
Expand Down Expand Up @@ -309,10 +311,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down Expand Up @@ -379,10 +383,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down Expand Up @@ -445,10 +451,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down Expand Up @@ -512,10 +520,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down
Expand Up @@ -152,10 +152,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': 'thread',
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': 'category1',
Expand Down Expand Up @@ -219,10 +221,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down Expand Up @@ -288,10 +292,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down Expand Up @@ -357,10 +363,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down Expand Up @@ -429,10 +437,12 @@ void main() {
'sound': 'sound.mp3',
'badgeNumber': 1,
'threadIdentifier': null,
'attachments': <Map<String, Object>>[
<String, Object>{
'attachments': <Map<String, Object?>>[
<String, Object?>{
'filePath': 'video.mp4',
'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373',
'hideThumbnail': null,
'thumbnailClippingRect': null,
}
],
'categoryIdentifier': null,
Expand Down

0 comments on commit cc0373b

Please sign in to comment.