diff --git a/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java b/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java index 19239da1..65dba35f 100644 --- a/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java +++ b/android/src/main/java/com/onesignal/flutter/OneSignalSerializer.java @@ -34,7 +34,11 @@ static HashMap convertNotificationToMap(INotification notificati if (notification.getGroupedNotifications() != null) { hash.put("groupKey", notification.getGroupKey()); hash.put("groupMessage", notification.getGroupMessage()); - hash.put("groupedNotifications", notification.getGroupedNotifications()); + List> groupedNotifications = new ArrayList<>(); + for (INotification groupedNotification : notification.getGroupedNotifications()) { + groupedNotifications.add(convertNotificationToMap(groupedNotification)); + } + hash.put("groupedNotifications", groupedNotifications); } hash.put("notificationId", notification.getNotificationId()); diff --git a/examples/demo-fm/README.md b/examples/demo-fm/README.md index dbd403a0..a1554312 100644 --- a/examples/demo-fm/README.md +++ b/examples/demo-fm/README.md @@ -14,3 +14,8 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## iOS Action Button Icons + +See [Testing iOS Action Button Icons](../ios-action-button-icons.md) to verify +the bundled Save Story and Share notification icons. diff --git a/examples/demo-fm/ios/Runner.xcodeproj/project.pbxproj b/examples/demo-fm/ios/Runner.xcodeproj/project.pbxproj index 88e857d6..ffdb8ade 100644 --- a/examples/demo-fm/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/demo-fm/ios/Runner.xcodeproj/project.pbxproj @@ -8,12 +8,16 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 387AB52C0DD18FF1163BC4CE /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 71711A7983E251AC8CEE5F28 /* template-bookmark-icon.png */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3D65C7BAD5F811862DD55B56 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 71711A7983E251AC8CEE5F28 /* template-bookmark-icon.png */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 */; }; + AD56AF222F6ED6510A4F5D81 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = D7B9B4AB51932DD15EF24798 /* template-share-icon.png */; }; + C8A8308E6AC9BAF88279C114 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = D7B9B4AB51932DD15EF24798 /* template-share-icon.png */; }; E8C8D1902F7B0DB7006581CB /* OneSignalNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E8C8D1892F7B0DB7006581CB /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E8C8D1B32F7D9550006581CB /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8C8D1B22F7D9550006581CB /* WidgetKit.framework */; }; E8C8D1B52F7D9550006581CB /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8C8D1B42F7D9550006581CB /* SwiftUI.framework */; }; @@ -67,6 +71,7 @@ 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 71711A7983E251AC8CEE5F28 /* template-bookmark-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-bookmark-icon.png"; 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 = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; @@ -78,6 +83,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D7B9B4AB51932DD15EF24798 /* template-share-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-share-icon.png"; sourceTree = ""; }; E8C8D1892F7B0DB7006581CB /* OneSignalNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E8C8D1B02F7D9550006581CB /* OneSignalWidgetExtensionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalWidgetExtensionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E8C8D1B22F7D9550006581CB /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -203,6 +209,8 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 71711A7983E251AC8CEE5F28 /* template-bookmark-icon.png */, + D7B9B4AB51932DD15EF24798 /* template-share-icon.png */, ); path = Runner; sourceTree = ""; @@ -345,6 +353,8 @@ E8C8D1D12F800000006581CB /* GoogleService-Info.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + 387AB52C0DD18FF1163BC4CE /* template-bookmark-icon.png in Resources */, + C8A8308E6AC9BAF88279C114 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -352,6 +362,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3D65C7BAD5F811862DD55B56 /* template-bookmark-icon.png in Resources */, + AD56AF222F6ED6510A4F5D81 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo-fm/ios/Runner/template-bookmark-icon.png b/examples/demo-fm/ios/Runner/template-bookmark-icon.png new file mode 100644 index 00000000..99281835 Binary files /dev/null and b/examples/demo-fm/ios/Runner/template-bookmark-icon.png differ diff --git a/examples/demo-fm/ios/Runner/template-share-icon.png b/examples/demo-fm/ios/Runner/template-share-icon.png new file mode 100644 index 00000000..2457e187 Binary files /dev/null and b/examples/demo-fm/ios/Runner/template-share-icon.png differ diff --git a/examples/demo-no-location-pods/README.md b/examples/demo-no-location-pods/README.md index a747298d..e1ccdab1 100644 --- a/examples/demo-no-location-pods/README.md +++ b/examples/demo-no-location-pods/README.md @@ -60,6 +60,9 @@ resolves when location is disabled. CocoaPods only builds one mismatched sets produce duplicate framework outputs and the build error reported in [GitHub issue #1173](https://github.com/OneSignal/OneSignal-Flutter-SDK/issues/1173). +To test the bundled notification action icons, follow +[Testing iOS Action Button Icons](../ios-action-button-icons.md). + ## Android Run with the same helper script: diff --git a/examples/demo-no-location-pods/ios/Runner.xcodeproj/project.pbxproj b/examples/demo-no-location-pods/ios/Runner.xcodeproj/project.pbxproj index 7e9d312a..237bd62a 100644 --- a/examples/demo-no-location-pods/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/demo-no-location-pods/ios/Runner.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 192FBDE3E5FC9B1E6BB19FBC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49248215A15C404DA16328DF /* Foundation.framework */; }; + 22345C06CA4949C3C8AA069B /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 2E9555FD21E8DFCB65A890A7 /* template-share-icon.png */; }; 2DFB8DA296AF02A4B363AA3B /* OneSignalWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6539668DF6C89BFDC76FCB84 /* OneSignalWidgetBundle.swift */; }; 2F288C153791A53B901B7A25 /* Pods_OneSignalWidgetExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6B16C9B4C335CDE40E0449F /* Pods_OneSignalWidgetExtension.framework */; }; 3B1B085652E2F6FCF2E0453E /* OneSignalWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FECE8682DCDD3961E46E889A /* OneSignalWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -18,6 +19,7 @@ 5EE48FCB903022F3E60E22DD /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 579EE555FAC49FE764750AB8 /* SwiftUI.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 8E8A1FA052CF751D7EF03EA3 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 702B7644E4D762408F8A9BE5 /* template-bookmark-icon.png */; }; 920F75D10B5ACDBBCEBCA71D /* Pods_OneSignalNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0685C57CD35C510FF6DA5C1 /* Pods_OneSignalNotificationServiceExtension.framework */; }; 96003FFCA81AF7F0D02FF192 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63245B01345B8BE3A1902785 /* WidgetKit.framework */; }; 9709DAE0290094C4ED9C37AF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49248215A15C404DA16328DF /* Foundation.framework */; }; @@ -26,6 +28,8 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; C0241F15C546B89B6A35090F /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47701A9BFD5F78B37CB0E87 /* NotificationService.swift */; }; E395919D263D3A7EE996872F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5FC8CF448F5485AF9CA242F /* Pods_Runner.framework */; }; + EC3E995823EDCDFAD7622F57 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 702B7644E4D762408F8A9BE5 /* template-bookmark-icon.png */; }; + F656A2DEE91710B22FFFDDEA /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 2E9555FD21E8DFCB65A890A7 /* template-share-icon.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -75,6 +79,7 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 283263A4CCECBE70BE46C806 /* OneSignalWidgetLiveActivity.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OneSignalWidgetLiveActivity.swift; sourceTree = ""; }; 2A5D002B7EAC6DFD6AACEFF6 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2E9555FD21E8DFCB65A890A7 /* template-share-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-share-icon.png"; sourceTree = ""; }; 35CC497FDF78C6584F9271C3 /* Pods-OneSignalNotificationServiceExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.profile.xcconfig"; path = "Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.profile.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 40066B592891CD9D37812A6C /* OneSignalNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = OneSignalNotificationServiceExtension.entitlements; sourceTree = ""; }; @@ -83,6 +88,7 @@ 579EE555FAC49FE764750AB8 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 63245B01345B8BE3A1902785 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 6539668DF6C89BFDC76FCB84 /* OneSignalWidgetBundle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OneSignalWidgetBundle.swift; sourceTree = ""; }; + 702B7644E4D762408F8A9BE5 /* template-bookmark-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-bookmark-icon.png"; 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 = ""; }; 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -237,6 +243,8 @@ 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, B40000000000000000000001 /* Runner.entitlements */, + 702B7644E4D762408F8A9BE5 /* template-bookmark-icon.png */, + 2E9555FD21E8DFCB65A890A7 /* template-share-icon.png */, ); path = Runner; sourceTree = ""; @@ -363,6 +371,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8E8A1FA052CF751D7EF03EA3 /* template-bookmark-icon.png in Resources */, + F656A2DEE91710B22FFFDDEA /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -374,6 +384,8 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + EC3E995823EDCDFAD7622F57 /* template-bookmark-icon.png in Resources */, + 22345C06CA4949C3C8AA069B /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo-no-location-pods/ios/Runner/template-bookmark-icon.png b/examples/demo-no-location-pods/ios/Runner/template-bookmark-icon.png new file mode 100644 index 00000000..99281835 Binary files /dev/null and b/examples/demo-no-location-pods/ios/Runner/template-bookmark-icon.png differ diff --git a/examples/demo-no-location-pods/ios/Runner/template-share-icon.png b/examples/demo-no-location-pods/ios/Runner/template-share-icon.png new file mode 100644 index 00000000..2457e187 Binary files /dev/null and b/examples/demo-no-location-pods/ios/Runner/template-share-icon.png differ diff --git a/examples/demo-no-location/README.md b/examples/demo-no-location/README.md index 3feda4e4..b0e4b4a4 100644 --- a/examples/demo-no-location/README.md +++ b/examples/demo-no-location/README.md @@ -52,6 +52,9 @@ The iOS project includes a `OneSignalNotificationServiceExtension` and a `OneSignalWidgetExtension` using Swift Package Manager. On iOS 16.2 or newer, use the app's **Start Live Activity** button to launch the sample widget. +To test the bundled notification action icons, follow +[Testing iOS Action Button Icons](../ios-action-button-icons.md). + ## Android Run with the same helper script: diff --git a/examples/demo-no-location/ios/Runner.xcodeproj/project.pbxproj b/examples/demo-no-location/ios/Runner.xcodeproj/project.pbxproj index dd2ddade..dbeeeb7c 100644 --- a/examples/demo-no-location/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/demo-no-location/ios/Runner.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 81721935A1C1A6B22D0FC7B6 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B8BD931218470F9C63269DE3 /* template-share-icon.png */; }; 82A717C11D665E1F17CE0805 /* OneSignalExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 91805F36348D16AC33C7D5F1 /* OneSignalExtension */; }; 8C82A425795B004071C2D17E /* OneSignalWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 80BDB394CE3DE731FCC5878B /* OneSignalWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -24,7 +25,10 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A4E37AE03CDC1BF28477FEF5 /* OneSignalWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93B5702926A8142C6303DB7 /* OneSignalWidgetBundle.swift */; }; A9B01134CB411B595607C5A5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F82AD17ACA999C1A5906D1E6 /* Foundation.framework */; }; + CD43A29374CB82B100D28169 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B8BD931218470F9C63269DE3 /* template-share-icon.png */; }; D84C4DEF279FD1D3E87E77EE /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD6D18F62B44DB9751DFADB /* SwiftUI.framework */; }; + E9FCC244F985FC546FE5F851 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E9793F9F4D3397328182FF8 /* template-bookmark-icon.png */; }; + ED234D389219D9D3E3C6AC11 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E9793F9F4D3397328182FF8 /* template-bookmark-icon.png */; }; F6CD8D8AD9B2E6BE0FE837D1 /* OneSignalNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 814EE513E76B5D478AC1C14C /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -84,6 +88,7 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 80BDB394CE3DE731FCC5878B /* OneSignalWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 814EE513E76B5D478AC1C14C /* OneSignalNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneSignalNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 8E9793F9F4D3397328182FF8 /* template-bookmark-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-bookmark-icon.png"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -93,6 +98,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B40000000000000000000001 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; B663C4730837080E9D812D72 /* NotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + B8BD931218470F9C63269DE3 /* template-share-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-share-icon.png"; sourceTree = ""; }; B93B5702926A8142C6303DB7 /* OneSignalWidgetBundle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OneSignalWidgetBundle.swift; sourceTree = ""; }; C830DE33B467A1DED62321BB /* OneSignalNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = OneSignalNotificationServiceExtension.entitlements; sourceTree = ""; }; DAD6D18F62B44DB9751DFADB /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; @@ -198,6 +204,8 @@ 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, B40000000000000000000001 /* Runner.entitlements */, + 8E9793F9F4D3397328182FF8 /* template-bookmark-icon.png */, + B8BD931218470F9C63269DE3 /* template-share-icon.png */, ); path = Runner; sourceTree = ""; @@ -341,6 +349,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E9FCC244F985FC546FE5F851 /* template-bookmark-icon.png in Resources */, + 81721935A1C1A6B22D0FC7B6 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -352,6 +362,8 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ED234D389219D9D3E3C6AC11 /* template-bookmark-icon.png in Resources */, + CD43A29374CB82B100D28169 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo-no-location/ios/Runner/template-bookmark-icon.png b/examples/demo-no-location/ios/Runner/template-bookmark-icon.png new file mode 100644 index 00000000..99281835 Binary files /dev/null and b/examples/demo-no-location/ios/Runner/template-bookmark-icon.png differ diff --git a/examples/demo-no-location/ios/Runner/template-share-icon.png b/examples/demo-no-location/ios/Runner/template-share-icon.png new file mode 100644 index 00000000..2457e187 Binary files /dev/null and b/examples/demo-no-location/ios/Runner/template-share-icon.png differ diff --git a/examples/demo-pods/README.md b/examples/demo-pods/README.md index 4645eab7..5d33f614 100644 --- a/examples/demo-pods/README.md +++ b/examples/demo-pods/README.md @@ -38,6 +38,9 @@ For iOS with multiple simulators: ../run-ios.sh ``` +To test the bundled iOS notification action icons, follow +[Testing iOS Action Button Icons](../ios-action-button-icons.md). + Or specify a device directly: ```bash diff --git a/examples/demo-pods/ios/Runner.xcodeproj/project.pbxproj b/examples/demo-pods/ios/Runner.xcodeproj/project.pbxproj index 7362b0de..e271d23c 100644 --- a/examples/demo-pods/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/demo-pods/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 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 */; }; + 9E8040A60FDA0353CA2D6948 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 3A5EEB34F6DCFDE209974132 /* template-bookmark-icon.png */; }; AABB00110000000000000005 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABB00110000000000000001 /* NotificationService.swift */; }; B2FEE5FC37F5386D74141E35 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 495D768A06CDFF67EB9ECFBD /* Pods_Runner.framework */; }; BBCC00220000000000000005 /* OneSignalWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCC00220000000000000001 /* OneSignalWidgetLiveActivity.swift */; }; @@ -21,9 +22,12 @@ BBCC00220000000000000014 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBCC00220000000000000012 /* WidgetKit.framework */; }; BBCC00220000000000000015 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBCC00220000000000000013 /* SwiftUI.framework */; }; CCDD00440000000000000002 /* vine_boom.wav in Resources */ = {isa = PBXBuildFile; fileRef = CCDD00440000000000000001 /* vine_boom.wav */; }; + CE359A5617DB32244E4779D0 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 84AE18978C8478FD2CED9139 /* template-share-icon.png */; }; DCC74E696EB253DBC854FB79 /* Pods_OneSignalNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA1153E70A142A707DAB6259 /* Pods_OneSignalNotificationServiceExtension.framework */; }; DDEE00330000000000000002 /* OneSignalNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AABB00110000000000000004 /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DDEE00330000000000000003 /* OneSignalWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = BBCC00220000000000000004 /* OneSignalWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + EE23C65AB2815C1497028A7C /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 84AE18978C8478FD2CED9139 /* template-share-icon.png */; }; + FF37A4A159CCB0EE6A18385C /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 3A5EEB34F6DCFDE209974132 /* template-bookmark-icon.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -72,6 +76,7 @@ 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 = ""; }; 214A4923A7D8B53C727C248F /* Pods-OneSignalWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-OneSignalWidgetExtension/Pods-OneSignalWidgetExtension.release.xcconfig"; sourceTree = ""; }; + 3A5EEB34F6DCFDE209974132 /* template-bookmark-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-bookmark-icon.png"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 463D6688A6D624CAA2FF3A9B /* Pods-OneSignalWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-OneSignalWidgetExtension/Pods-OneSignalWidgetExtension.debug.xcconfig"; sourceTree = ""; }; 495D768A06CDFF67EB9ECFBD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -80,6 +85,7 @@ 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 = ""; }; 7B6B6AC2646AE48D486BE87C /* Pods-OneSignalNotificationServiceExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.profile.xcconfig"; path = "Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.profile.xcconfig"; sourceTree = ""; }; + 84AE18978C8478FD2CED9139 /* template-share-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-share-icon.png"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -213,6 +219,8 @@ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, CCDD00440000000000000001 /* vine_boom.wav */, + 3A5EEB34F6DCFDE209974132 /* template-bookmark-icon.png */, + 84AE18978C8478FD2CED9139 /* template-share-icon.png */, ); path = Runner; sourceTree = ""; @@ -357,6 +365,8 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, CCDD00440000000000000002 /* vine_boom.wav in Resources */, + 9E8040A60FDA0353CA2D6948 /* template-bookmark-icon.png in Resources */, + CE359A5617DB32244E4779D0 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -364,6 +374,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + FF37A4A159CCB0EE6A18385C /* template-bookmark-icon.png in Resources */, + EE23C65AB2815C1497028A7C /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo-pods/ios/Runner/template-bookmark-icon.png b/examples/demo-pods/ios/Runner/template-bookmark-icon.png new file mode 100644 index 00000000..99281835 Binary files /dev/null and b/examples/demo-pods/ios/Runner/template-bookmark-icon.png differ diff --git a/examples/demo-pods/ios/Runner/template-share-icon.png b/examples/demo-pods/ios/Runner/template-share-icon.png new file mode 100644 index 00000000..2457e187 Binary files /dev/null and b/examples/demo-pods/ios/Runner/template-share-icon.png differ diff --git a/examples/demo/README.md b/examples/demo/README.md index dbd403a0..a1554312 100644 --- a/examples/demo/README.md +++ b/examples/demo/README.md @@ -14,3 +14,8 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## iOS Action Button Icons + +See [Testing iOS Action Button Icons](../ios-action-button-icons.md) to verify +the bundled Save Story and Share notification icons. diff --git a/examples/demo/ios/Runner.xcodeproj/project.pbxproj b/examples/demo/ios/Runner.xcodeproj/project.pbxproj index 2819faa2..09fddfcb 100644 --- a/examples/demo/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/demo/ios/Runner.xcodeproj/project.pbxproj @@ -8,9 +8,12 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 20CE5C183D694EBEEDD442B8 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 34C13E89A3F982FBA62470E9 /* template-share-icon.png */; }; + 2853B38FBE7C91C90BF57E64 /* template-share-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 34C13E89A3F982FBA62470E9 /* template-share-icon.png */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 93AD72954BA7884C3B098FEC /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 8C681F8F27BF20E00DAF604E /* template-bookmark-icon.png */; }; 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 */; }; @@ -18,6 +21,7 @@ E8C8D1B32F7D9550006581CB /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8C8D1B22F7D9550006581CB /* WidgetKit.framework */; }; E8C8D1B52F7D9550006581CB /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8C8D1B42F7D9550006581CB /* SwiftUI.framework */; }; E8C8D1C22F7D9551006581CB /* OneSignalWidgetExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E8C8D1B02F7D9550006581CB /* OneSignalWidgetExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + F7477E617F9DF1999D6E5AF0 /* template-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 8C681F8F27BF20E00DAF604E /* template-bookmark-icon.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -65,11 +69,13 @@ /* Begin PBXFileReference section */ 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 = ""; }; + 34C13E89A3F982FBA62470E9 /* template-share-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-share-icon.png"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; 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 = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8C681F8F27BF20E00DAF604E /* template-bookmark-icon.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; path = "template-bookmark-icon.png"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -200,6 +206,8 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 8C681F8F27BF20E00DAF604E /* template-bookmark-icon.png */, + 34C13E89A3F982FBA62470E9 /* template-share-icon.png */, ); path = Runner; sourceTree = ""; @@ -341,6 +349,8 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + 93AD72954BA7884C3B098FEC /* template-bookmark-icon.png in Resources */, + 2853B38FBE7C91C90BF57E64 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -348,6 +358,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F7477E617F9DF1999D6E5AF0 /* template-bookmark-icon.png in Resources */, + 20CE5C183D694EBEEDD442B8 /* template-share-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo/ios/Runner/template-bookmark-icon.png b/examples/demo/ios/Runner/template-bookmark-icon.png new file mode 100644 index 00000000..99281835 Binary files /dev/null and b/examples/demo/ios/Runner/template-bookmark-icon.png differ diff --git a/examples/demo/ios/Runner/template-share-icon.png b/examples/demo/ios/Runner/template-share-icon.png new file mode 100644 index 00000000..2457e187 Binary files /dev/null and b/examples/demo/ios/Runner/template-share-icon.png differ diff --git a/examples/demo/lib/main.dart b/examples/demo/lib/main.dart index 223823be..51e6bac3 100644 --- a/examples/demo/lib/main.dart +++ b/examples/demo/lib/main.dart @@ -26,7 +26,8 @@ Future main() async { await prefs.init(); final envAppId = dotenv.env['ONESIGNAL_APP_ID']; - final appId = (envAppId != null && envAppId.isNotEmpty) ? envAppId : _defaultAppId; + final appId = + (envAppId != null && envAppId.isNotEmpty) ? envAppId : _defaultAppId; // Initialize OneSignal SDK OneSignal.Debug.setLogLevel(OSLogLevel.verbose); @@ -47,31 +48,54 @@ Future main() async { // Register IAM listeners OneSignal.InAppMessages.addWillDisplayListener((event) { - debugPrint('IAM will display: ${event.message.messageId}'); + debugPrint('[OneSignal] IAM willDisplay: ${event.message.messageId}'); }); OneSignal.InAppMessages.addDidDisplayListener((event) { - debugPrint('IAM did display: ${event.message.messageId}'); + debugPrint('[OneSignal] IAM didDisplay: ${event.message.messageId}'); }); OneSignal.InAppMessages.addWillDismissListener((event) { - debugPrint('IAM will dismiss: ${event.message.messageId}'); + debugPrint('[OneSignal] IAM willDismiss: ${event.message.messageId}'); }); OneSignal.InAppMessages.addDidDismissListener((event) { - debugPrint('IAM did dismiss: ${event.message.messageId}'); + debugPrint('[OneSignal] IAM didDismiss: ${event.message.messageId}'); }); OneSignal.InAppMessages.addClickListener((event) { - debugPrint('IAM clicked: ${event.message.messageId}'); + debugPrint('[OneSignal] IAM click: ${event.message.messageId}'); }); // Register notification listeners OneSignal.Notifications.addClickListener((event) { - debugPrint('Notification clicked: ${event.notification.title}'); + debugPrint( + '[OneSignal] Notification click: ${event.notification.title ?? ''}', + ); + + // Uncomment to see the full event object. + // debugPrint('[OneSignal] event: ${event.jsonRepresentation()}'); }); + OneSignal.Notifications.addForegroundWillDisplayListener((event) { debugPrint( - 'Notification foreground will display: ${event.notification.title}', + '[OneSignal] Notification foregroundWillDisplay: ' + '${event.notification.title ?? ''}', ); - // event.preventDefault(); // This will prevent the notification from being displayed - // event.notification.display(); // This will override the preventDefault and display the notification + + // Uncomment to test preventing the default display behavior. + // event.preventDefault(); + + // Can be called after preventDefault (within ~25 seconds) to force display. + // event.notification.display(); + + // Example with a delay (assumes preventDefault was called). + // debugPrint('[OneSignal] Forcing notification display in 24 seconds'); + // () async { + // var seconds = 24; + // while (seconds > 0) { + // await Future.delayed(const Duration(seconds: 1)); + // seconds--; + // debugPrint('[OneSignal] Displaying notification in $seconds seconds'); + // } + // event.notification.display(); + // }(); }); // Set up API service @@ -79,16 +103,17 @@ Future main() async { try { apiKey = dotenv.env['ONESIGNAL_API_KEY'] ?? ''; } catch (_) { - debugPrint('API key not found, continuing without it'); + debugPrint('[OneSignal] API key not found, continuing without it'); } - final apiService = OneSignalApiService() - ..setAppId(appId) - ..setApiKey(apiKey); + final apiService = + OneSignalApiService() + ..setAppId(appId) + ..setApiKey(apiKey); // Fetch tooltips in background TooltipHelper().init(); - debugPrint('OneSignal initialized with app ID: $appId'); + debugPrint('[OneSignal] Initialized with app ID: $appId'); runApp( ChangeNotifierProvider( diff --git a/examples/demo/lib/viewmodels/app_viewmodel.dart b/examples/demo/lib/viewmodels/app_viewmodel.dart index 2a8d5b43..e2d69e96 100644 --- a/examples/demo/lib/viewmodels/app_viewmodel.dart +++ b/examples/demo/lib/viewmodels/app_viewmodel.dart @@ -169,7 +169,7 @@ class AppViewModel extends ChangeNotifier { } debugPrint( - 'Push subscription changed: ' + '[OneSignal] Push subscription changed: ' 'id=${state.previous.id ?? 'null'} → ${state.current.id ?? 'null'}, ' 'optedIn=${state.previous.optedIn} → ${state.current.optedIn}, ' 'token=${fmtToken(state.previous.token)} → ${fmtToken(state.current.token)}', @@ -179,14 +179,15 @@ class AppViewModel extends ChangeNotifier { OneSignal.Notifications.addPermissionObserver((permission) { _hasNotificationPermission = permission; - debugPrint('Permission changed: $permission'); + debugPrint('[OneSignal] Permission changed: $permission'); notifyListeners(); }); OneSignal.User.addObserver((state) { final nextOnesignalId = state.current.onesignalId; debugPrint( - 'User changed: onesignalId=${nextOnesignalId ?? 'null'}, externalId=${state.current.externalId ?? 'null'}', + '[OneSignal] User changed: onesignalId=${nextOnesignalId ?? 'null'}, ' + 'externalId=${state.current.externalId ?? 'null'}', ); _oneSignalId = nextOnesignalId; diff --git a/examples/ios-action-button-icons.md b/examples/ios-action-button-icons.md new file mode 100644 index 00000000..b21e4c37 --- /dev/null +++ b/examples/ios-action-button-icons.md @@ -0,0 +1,13 @@ +# Testing iOS Action Button Icons + +The iOS OneSignal demos bundle two shared action button icons in both the +`Runner` and `OneSignalNotificationServiceExtension` targets. + +Create an image notification in the OneSignal dashboard with these action +buttons: + +- **Save Story** with icon `template-bookmark-icon` +- **Share** with icon `template-share-icon` + +Enter the icon names without the `.png` extension. On an iOS 15 or newer +physical device, expand the notification to verify that both icons render. diff --git a/lib/src/inappmessage.dart b/lib/src/inappmessage.dart index 5fc1c094..c8f4960a 100644 --- a/lib/src/inappmessage.dart +++ b/lib/src/inappmessage.dart @@ -16,8 +16,8 @@ class OSInAppMessageClickEvent extends JSONStringRepresentable { String jsonRepresentation() { return convertToJsonString({ - 'message': this.message.jsonRepresentation(), - 'result': this.result.jsonRepresentation(), + 'message': this.message.mapRepresentation(), + 'result': this.result.mapRepresentation(), }); } } @@ -41,13 +41,13 @@ class OSInAppMessageClickResult extends JSONStringRepresentable { this.closingMessage = json["closing_message"] as bool; } - String jsonRepresentation() { - return convertToJsonString({ - 'action_id': this.actionId, - 'url': this.url, - 'closing_message': this.closingMessage, - }); - } + Map mapRepresentation() => { + 'action_id': this.actionId, + 'url': this.url, + 'closing_message': this.closingMessage, + }; + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } class OSInAppMessage extends JSONStringRepresentable { @@ -57,9 +57,9 @@ class OSInAppMessage extends JSONStringRepresentable { this.messageId = json["message_id"]; } - String jsonRepresentation() { - return convertToJsonString({'message_id': this.messageId}); - } + Map mapRepresentation() => {'message_id': this.messageId}; + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } class OSInAppMessageWillDisplayEvent extends JSONStringRepresentable { @@ -70,7 +70,7 @@ class OSInAppMessageWillDisplayEvent extends JSONStringRepresentable { } String jsonRepresentation() { - return convertToJsonString({'message': this.message.jsonRepresentation()}); + return convertToJsonString({'message': this.message.mapRepresentation()}); } } @@ -82,7 +82,7 @@ class OSInAppMessageDidDisplayEvent extends JSONStringRepresentable { } String jsonRepresentation() { - return convertToJsonString({'message': this.message.jsonRepresentation()}); + return convertToJsonString({'message': this.message.mapRepresentation()}); } } @@ -94,7 +94,7 @@ class OSInAppMessageWillDismissEvent extends JSONStringRepresentable { } String jsonRepresentation() { - return convertToJsonString({'message': this.message.jsonRepresentation()}); + return convertToJsonString({'message': this.message.mapRepresentation()}); } } @@ -106,6 +106,6 @@ class OSInAppMessageDidDismissEvent extends JSONStringRepresentable { } String jsonRepresentation() { - return convertToJsonString({'message': this.message.jsonRepresentation()}); + return convertToJsonString({'message': this.message.mapRepresentation()}); } } diff --git a/lib/src/notification.dart b/lib/src/notification.dart index 9f403f4e..e187b900 100644 --- a/lib/src/notification.dart +++ b/lib/src/notification.dart @@ -225,10 +225,12 @@ class OSNotification extends JSONStringRepresentable { } if (json.containsKey('groupedNotifications')) { final dynamic jsonGroupedNotifications = json['groupedNotifications']; - final jsonList = - jsonDecode(jsonGroupedNotifications.toString()) as List; + final List jsonList = jsonGroupedNotifications is String + ? jsonDecode(jsonGroupedNotifications) as List + : jsonGroupedNotifications as List; this.groupedNotifications = jsonList - .map((dynamic item) => OSNotification(item as Map)) + .map((dynamic item) => + OSNotification((item as Map).cast())) .toList(); } @@ -264,7 +266,59 @@ class OSNotification extends JSONStringRepresentable { } } - String jsonRepresentation() => convertToJsonString(this.rawPayload); + Map mapRepresentation() { + return { + 'notificationId': this.notificationId, + if (this.templateId != null) 'templateId': this.templateId, + if (this.templateName != null) 'templateName': this.templateName, + if (this.sound != null) 'sound': this.sound, + if (this.title != null) 'title': this.title, + if (this.body != null) 'body': this.body, + if (this.launchUrl != null) 'launchUrl': this.launchUrl, + if (this.additionalData != null) 'additionalData': this.additionalData, + if (this.buttons != null) + 'buttons': + this.buttons!.map((button) => button.mapRepresentation()).toList(), + if (this.rawPayload != null) 'rawPayload': this.rawPayload, + if (this.attachments != null) 'attachments': this.attachments, + if (this.contentAvailable != null) + 'contentAvailable': this.contentAvailable, + if (this.mutableContent != null) 'mutableContent': this.mutableContent, + if (this.category != null) 'category': this.category, + if (this.badge != null) 'badge': this.badge, + if (this.badgeIncrement != null) 'badgeIncrement': this.badgeIncrement, + if (this.subtitle != null) 'subtitle': this.subtitle, + if (this.relevanceScore != null) 'relevanceScore': this.relevanceScore, + if (this.interruptionLevel != null) + 'interruptionLevel': this.interruptionLevel, + if (this.groupedNotifications != null) + 'groupedNotifications': this + .groupedNotifications! + .map((notification) => notification.mapRepresentation()) + .toList(), + if (this.androidNotificationId != null) + 'androidNotificationId': this.androidNotificationId, + if (this.smallIcon != null) 'smallIcon': this.smallIcon, + if (this.largeIcon != null) 'largeIcon': this.largeIcon, + if (this.bigPicture != null) 'bigPicture': this.bigPicture, + if (this.smallIconAccentColor != null) + 'smallIconAccentColor': this.smallIconAccentColor, + if (this.ledColor != null) 'ledColor': this.ledColor, + if (this.lockScreenVisibility != null) + 'lockScreenVisibility': this.lockScreenVisibility, + if (this.groupKey != null) 'groupKey': this.groupKey, + if (this.groupMessage != null) 'groupMessage': this.groupMessage, + if (this.fromProjectNumber != null) + 'fromProjectNumber': this.fromProjectNumber, + if (this.collapseId != null) 'collapseId': this.collapseId, + if (this.priority != null) 'priority': this.priority, + if (this.backgroundImageLayout != null) + 'backgroundImageLayout': + this.backgroundImageLayout!.mapRepresentation(), + }; + } + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } /// An instance of this class represents a user interaction with @@ -280,9 +334,10 @@ class OSNotificationClickResult extends JSONStringRepresentable { this.url = json['url']; } - String jsonRepresentation() { - return convertToJsonString({'action_id': this.actionId, 'url': this.url}); - } + Map mapRepresentation() => + {'action_id': this.actionId, 'url': this.url}; + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } /// Represents a button sent as part of a push notification @@ -298,17 +353,41 @@ class OSActionButton extends JSONStringRepresentable { /// button's icon String? icon; - OSActionButton({required this.id, required this.text, this.icon}); + /// (iOS only) + /// The SF Symbol name for the button's icon. + String? systemIcon; + + /// (iOS only) + /// The name of an image in the app bundle for the button's icon. + String? templateIcon; + + OSActionButton({ + required this.id, + required this.text, + this.icon, + this.systemIcon, + this.templateIcon, + }); OSActionButton.fromJson(Map json) { this.id = json['id'] as String; this.text = json['text'] as String; if (json.containsKey('icon')) this.icon = json['icon'] as String?; + if (json.containsKey('systemIcon')) + this.systemIcon = json['systemIcon'] as String?; + if (json.containsKey('templateIcon')) + this.templateIcon = json['templateIcon'] as String?; } Map mapRepresentation() { - return {'id': this.id, 'text': this.text, 'icon': this.icon}; + return { + 'id': this.id, + 'text': this.text, + if (this.icon != null) 'icon': this.icon, + if (this.systemIcon != null) 'systemIcon': this.systemIcon, + if (this.templateIcon != null) 'templateIcon': this.templateIcon, + }; } String jsonRepresentation() { @@ -340,13 +419,15 @@ class OSAndroidBackgroundImageLayout extends JSONStringRepresentable { this.bodyTextColor = json['bodyTextColor'] as String?; } - String jsonRepresentation() { - return convertToJsonString({ + Map mapRepresentation() { + return { 'image': this.image, 'titleTextColor': this.titleTextColor, 'bodyTextColor': this.bodyTextColor - }); + }; } + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } extension OSDisplayNotification on OSNotification { @@ -368,7 +449,7 @@ class OSNotificationWillDisplayEvent extends JSONStringRepresentable { String jsonRepresentation() { return convertToJsonString( - {'notification': this.notification.jsonRepresentation()}); + {'notification': this.notification.mapRepresentation()}); } } @@ -387,8 +468,8 @@ class OSNotificationClickEvent extends JSONStringRepresentable { String jsonRepresentation() { return convertToJsonString({ - 'notification': this.notification.jsonRepresentation(), - 'result': this.result.jsonRepresentation() + 'notification': this.notification.mapRepresentation(), + 'result': this.result.mapRepresentation() }); } } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index b331e989..36af1f74 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -15,10 +15,10 @@ class OSPushSubscriptionState extends JSONStringRepresentable { this.optedIn = json['optedIn'] as bool; } - String jsonRepresentation() { - return convertToJsonString( - {'id': this.id, 'token': this.token, 'optedIn': this.optedIn}); - } + Map mapRepresentation() => + {'id': this.id, 'token': this.token, 'optedIn': this.optedIn}; + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } /// An instance of this class describes a change in the user's OneSignal @@ -39,8 +39,8 @@ class OSPushSubscriptionChangedState extends JSONStringRepresentable { String jsonRepresentation() { return convertToJsonString({ - 'current': current.jsonRepresentation(), - 'previous': previous.jsonRepresentation() + 'current': current.mapRepresentation(), + 'previous': previous.mapRepresentation() }); } } diff --git a/lib/src/user.dart b/lib/src/user.dart index f72627a4..cc8dfa43 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -18,12 +18,12 @@ class OSUserState extends JSONStringRepresentable { this.externalId = json['externalId'] as String?; } - String jsonRepresentation() { - return convertToJsonString({ - 'onesignalId': this.onesignalId, - 'externalId': this.externalId, - }); - } + Map mapRepresentation() => { + 'onesignalId': this.onesignalId, + 'externalId': this.externalId, + }; + + String jsonRepresentation() => convertToJsonString(mapRepresentation()); } /// An instance of this class describes a change in the user state. @@ -37,7 +37,7 @@ class OSUserChangedState extends JSONStringRepresentable { String jsonRepresentation() { return convertToJsonString({ - 'current': current.jsonRepresentation(), + 'current': current.mapRepresentation(), }); } } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b931cd46..ef291b54 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -5,8 +5,5 @@ abstract class JSONStringRepresentable { String jsonRepresentation(); String convertToJsonString(Map? object) => - JsonEncoder.withIndent(' ') - .convert(object) - .replaceAll("\\n", "\n") - .replaceAll("\\", ""); + JsonEncoder.withIndent(' ').convert(object); } diff --git a/test/notification_test.dart b/test/notification_test.dart index 2e41a6ec..688c34c1 100644 --- a/test/notification_test.dart +++ b/test/notification_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter_test/flutter_test.dart'; import 'package:onesignal_flutter/onesignal_flutter.dart'; import 'package:onesignal_flutter/src/notification.dart'; @@ -172,6 +174,30 @@ void main() { expect(notification.groupedNotifications![2].title, 'Title 3'); }); + test('parses grouped notifications from the Android method channel', () { + final json = { + 'notificationId': 'parent-notification', + 'groupedNotifications': [ + { + 'notificationId': 'grouped-1', + 'title': 'Title 1', + }, + { + 'notificationId': 'grouped-2', + 'title': 'Title 2', + }, + ], + }; + + final notification = OSNotification(json); + + expect(notification.groupedNotifications, hasLength(2)); + expect(notification.groupedNotifications![0].notificationId, 'grouped-1'); + expect(notification.groupedNotifications![0].title, 'Title 1'); + expect(notification.groupedNotifications![1].notificationId, 'grouped-2'); + expect(notification.groupedNotifications![1].title, 'Title 2'); + }); + test('creates with empty grouped notifications', () { final groupedNotificationsJson = '[]'; @@ -188,10 +214,44 @@ void main() { test('jsonRepresentation returns correct JSON string', () { final notification = OSNotification(validNotificationJson); final jsonRep = notification.jsonRepresentation(); + final decoded = jsonDecode(jsonRep) as Map; + + expect(decoded['notificationId'], 'notification-123'); + expect(decoded['title'], 'Test Title'); + expect(decoded['body'], 'Test Body'); + expect(decoded['rawPayload'], {'key': 'value'}); + expect(decoded['additionalData'], {'custom_key': 'custom_value'}); + expect(decoded['buttons'], hasLength(2)); + expect(decoded['buttons'][1], { + 'id': 'btn2', + 'text': 'Button 2', + 'icon': 'icon.png', + }); + }); - expect(jsonRep, isA()); - expect(jsonRep, contains('key')); - expect(jsonRep, contains('value')); + test('jsonRepresentation includes grouped notifications as objects', () { + final notification = OSNotification({ + ...androidOnlyJson, + 'groupedNotifications': [ + { + 'notificationId': 'grouped-1', + 'title': 'Title 1', + 'rawPayload': '{"grouped":true}', + }, + ], + }); + + final decoded = + jsonDecode(notification.jsonRepresentation()) as Map; + final grouped = decoded['groupedNotifications'] as List; + + expect(decoded['groupKey'], 'group_key_1'); + expect(decoded['androidNotificationId'], 1); + expect(grouped.single, { + 'notificationId': 'grouped-1', + 'title': 'Title 1', + 'rawPayload': {'grouped': true}, + }); }); }); @@ -277,6 +337,49 @@ void main() { expect(button.icon, isNull); }); + test('creates from JSON with an iOS system icon', () { + final button = OSActionButton.fromJson({ + 'id': 'action3', + 'text': 'Save', + 'systemIcon': 'bookmark', + }); + + expect(button.systemIcon, 'bookmark'); + expect(button.templateIcon, isNull); + expect(button.icon, isNull); + expect(button.mapRepresentation(), { + 'id': 'action3', + 'text': 'Save', + 'systemIcon': 'bookmark', + }); + }); + + test('creates from JSON with an iOS template icon', () { + final button = OSActionButton.fromJson({ + 'id': 'action4', + 'text': 'Share', + 'templateIcon': 'share-icon', + }); + + expect(button.templateIcon, 'share-icon'); + expect(button.systemIcon, isNull); + expect(button.icon, isNull); + expect(button.mapRepresentation(), { + 'id': 'action4', + 'text': 'Share', + 'templateIcon': 'share-icon', + }); + }); + + test('mapRepresentation omits absent icons', () { + final button = OSActionButton(id: 'action5', text: 'Dismiss'); + + expect(button.mapRepresentation(), { + 'id': 'action5', + 'text': 'Dismiss', + }); + }); + test('mapRepresentation returns correct map', () { final button = OSActionButton( id: 'btn', @@ -368,9 +471,11 @@ void main() { }; final event = OSNotificationWillDisplayEvent(json); final jsonRep = event.jsonRepresentation(); + final decoded = jsonDecode(jsonRep) as Map; - expect(jsonRep, isA()); - expect(jsonRep, contains('notification')); + expect(decoded['notification'], isA>()); + expect(decoded['notification']['notificationId'], 'notification-123'); + expect(decoded['notification']['rawPayload'], {'key': 'value'}); }); }); @@ -408,10 +513,11 @@ void main() { }; final event = OSNotificationClickEvent(json); final jsonRep = event.jsonRepresentation(); + final decoded = jsonDecode(jsonRep) as Map; - expect(jsonRep, isA()); - expect(jsonRep, contains('notification')); - expect(jsonRep, contains('result')); + expect(decoded['notification'], isA>()); + expect(decoded['notification']['notificationId'], 'notification-123'); + expect(decoded['result'], clickResultJson); }); test('jsonRepresentation includes action data from result', () { @@ -422,8 +528,6 @@ void main() { final event = OSNotificationClickEvent(json); final jsonRep = event.jsonRepresentation(); - // The notification's jsonRepresentation only includes rawPayload - // The result includes actionId and url expect(jsonRep, contains('action-123')); expect(jsonRep, contains('https://example.com/action')); }); diff --git a/test/utils_test.dart b/test/utils_test.dart index 9b74d166..046cedbf 100644 --- a/test/utils_test.dart +++ b/test/utils_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter_test/flutter_test.dart'; import 'package:onesignal_flutter/src/utils.dart'; @@ -63,22 +65,24 @@ void main() { expect(result, equals('{}')); }); - test('convertToJsonString removes escaped newlines', () { + test('convertToJsonString preserves escaped newlines as valid JSON', () { final testObj = TestJSONStringRepresentable(); final map = {'text': 'line1\nline2'}; final result = testObj.convertToJsonString(map); - expect(result, contains('"text": "line1\nline2"')); + expect(result, contains(r'"text": "line1\nline2"')); + expect(jsonDecode(result), map); }); - test('convertToJsonString removes escaped backslashes', () { + test('convertToJsonString preserves escaped backslashes as valid JSON', () { final testObj = TestJSONStringRepresentable(); - final map = {'path': 'folder/file'}; + final map = {'path': r'folder\file'}; final result = testObj.convertToJsonString(map); - expect(result, contains('"path": "folder/file"')); + expect(result, contains(r'"path": "folder\\file"')); + expect(jsonDecode(result), map); }); test('convertToJsonString formats with proper indentation', () {