Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature -> iOS: Spam notifications on ring when app was killed #199

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions example/ios/Runner.xcodeproj/project.pbxproj
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il faudrait pas que ce fichier soit modifié.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
39EF213274CDCE732F2B8C44 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98E1EA7FBDDCA7752F4C0FE7 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
7484BE252BE9100400D9AC5F /* marimba.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7484BE242BE9100400D9AC5F /* marimba.mp3 */; };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il faudrait pas qu'il y ait d'audio importé dans le projet car il faudrait que ça puisse être n'importe quel audio et non un audio prédéfini.

74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
Expand All @@ -36,6 +37,7 @@
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4C51B4027C4F7781923E9AC0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
5CF1ED7B87324F801B4E8FD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
7484BE242BE9100400D9AC5F /* marimba.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = marimba.mp3; path = ../assets/marimba.mp3; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
Expand Down Expand Up @@ -93,6 +95,7 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
7484BE242BE9100400D9AC5F /* marimba.mp3 */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
Expand Down Expand Up @@ -192,6 +195,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
7484BE252BE9100400D9AC5F /* marimba.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -360,15 +364,15 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 63LD84R3KS;
DEVELOPMENT_TEAM = LUU9F35TKJ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample12;
Atsug0 marked this conversation as resolved.
Show resolved Hide resolved
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
Expand Down Expand Up @@ -496,15 +500,15 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 63LD84R3KS;
DEVELOPMENT_TEAM = LUU9F35TKJ;
Atsug0 marked this conversation as resolved.
Show resolved Hide resolved
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample12;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
Expand All @@ -524,15 +528,15 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 63LD84R3KS;
DEVELOPMENT_TEAM = LUU9F35TKJ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample12;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
Expand Down
1 change: 0 additions & 1 deletion example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import alarm
) -> Bool {
UNUserNotificationCenter.current().delegate = self
SwiftAlarmPlugin.registerBackgroundTasks()

GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
Expand Down
20 changes: 10 additions & 10 deletions example/lib/screens/edit_alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,16 @@ class _ExampleAlarmEditScreenState extends State<ExampleAlarmEditScreen> {
: widget.alarmSettings!.id;

final alarmSettings = AlarmSettings(
id: id,
dateTime: selectedDateTime,
loopAudio: loopAudio,
vibrate: vibrate,
volume: volume,
assetAudioPath: assetAudio,
notificationTitle: 'Alarm example',
notificationBody: 'Your alarm ($id) is ringing',
enableNotificationOnKill: Platform.isIOS,
);
id: id,
dateTime: selectedDateTime,
loopAudio: loopAudio,
vibrate: vibrate,
volume: volume,
assetAudioPath: assetAudio,
notificationTitle: 'Alarm example',
notificationBody: 'Your alarm ($id) is ringing',
enableNotificationOnKill: Platform.isIOS,
);
return alarmSettings;
}

Expand Down
4 changes: 3 additions & 1 deletion ios/Classes/AlarmConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ class AlarmConfiguration {
var triggerTime: Date?
var audioPlayer: AVAudioPlayer?
var timer: Timer?
var numberOfLoops: Int
var task: DispatchWorkItem?

init(id: Int, assetAudio: String, vibrationsEnabled: Bool, loopAudio: Bool, fadeDuration: Double, volume: Float?) {
init(id: Int, assetAudio: String, vibrationsEnabled: Bool, loopAudio: Bool, fadeDuration: Double, volume: Float?, numberOfLoops: Int) {
self.id = id
self.assetAudio = assetAudio
self.vibrationsEnabled = vibrationsEnabled
self.loopAudio = loopAudio
self.fadeDuration = fadeDuration
self.volume = volume
self.numberOfLoops = numberOfLoops
}
}
59 changes: 55 additions & 4 deletions ios/Classes/SwiftAlarmPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
}
}

func calculateTimeAfterAddingSeconds(_ seconds: Double) -> (hour: Int, minute: Int)? {
let now = Date()

let calendar = Calendar.current

if let newDate = calendar.date(byAdding: .second, value: Int(seconds), to: now) {
let hour = calendar.component(.hour, from: newDate)
let minute = calendar.component(.minute, from: newDate)
return (hour, minute)
} else {
return nil
}
}
func secondsToHoursMinutes(seconds: Double) -> (Int, Int) {
let secondsInt = Int(seconds)
let hours = secondsInt / 3600
let minutes = (secondsInt % 3600) / 60
return (hours, minutes)
}

private func setAlarm(call: FlutterMethodCall, result: FlutterResult) {
self.mixOtherAudios()

Expand All @@ -77,7 +97,12 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
let loopAudio = args["loopAudio"] as? Bool,
let fadeDuration = args["fadeDuration"] as? Double,
let vibrationsEnabled = args["vibrate"] as? Bool,
let assetAudio = args["assetAudio"] as? String else {
let assetAudio = args["assetAudio"] as? String,
let spamNotifOnKillIos = args["spamNotifOnKillIos"] as? Bool,
let nbrOfRepeat = args["nbrOfNotification"] as? Int,
let duration = args["durationBetweenNotification"] as? Int,
let notificationSound = args["notificationSound"] as? String
else {
result(FlutterError(code: "NATIVE_ERR", message: "[SwiftAlarmPlugin] Arguments are not in the expected format: \(call.arguments)", details: nil))
return
}
Expand All @@ -93,7 +118,8 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
vibrationsEnabled: vibrationsEnabled,
loopAudio: loopAudio,
fadeDuration: fadeDuration,
volume: volumeFloat
volume: volumeFloat,
numberOfLoops: nbrOfRepeat ?? 10
)
self.alarms[id] = alarmConfig

Expand All @@ -106,7 +132,15 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
}
}
}

if spamNotifOnKillIos == true {
let (hour, minute) = calculateTimeAfterAddingSeconds(args["delayInSeconds"] as? Double ?? 0) ?? (7, 30)
var title = notificationTitle ?? "TEST"
let body = notificationBody ?? "TEST"
let sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: notificationSound ?? ""))
NotificationManager.shared.programLocalNotif(nbrOfRepeat: nbrOfRepeat ?? 10, duration: duration ?? 10, hour: hour, minute: minute + 1, title: title, body: body, sound: sound)
}


notifOnKillEnabled = (args["notifOnKillEnabled"] as! Bool)
notificationTitleOnKill = (args["notifTitleOnAppKill"] as! String)
notificationBodyOnKill = (args["notifDescriptionOnAppKill"] as! String)
Expand Down Expand Up @@ -156,6 +190,22 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
}
}

func getAudioURL(forAsset assetAudio: String) -> URL? {
if assetAudio.hasPrefix("assets/") {
// Load audio from assets
let filename = registrar.lookupKey(forAsset: assetAudio)
guard let audioPath = Bundle.main.url(forResource: filename, withExtension: nil) else {
NSLog("[SwiftAlarmPlugin] Audio file not found: \(assetAudio)")
return nil
}
return audioPath
} else {
// Load audio from documents directory
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return documentsDirectory.appendingPathComponent(assetAudio)
}
}

private func loadAudioPlayer(withAsset assetAudio: String, forId id: Int) -> AVAudioPlayer? {
let audioURL: URL
if assetAudio.hasPrefix("assets/") {
Expand All @@ -182,6 +232,7 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {

@objc func executeTask(_ timer: Timer) {
if let id = timer.userInfo as? Int, let task = alarms[id]?.task {
NotificationManager.shared.cancelNotificationBis(nbrOfRepeat: alarms[id]?.numberOfLoops ?? 10)
task.perform()
}
}
Expand Down Expand Up @@ -273,6 +324,7 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
private func stopAlarm(id: Int, cancelNotif: Bool, result: FlutterResult) {
if cancelNotif {
NotificationManager.shared.cancelNotification(id: String(id))
NotificationManager.shared.cancelNotificationBis(nbrOfRepeat: self.alarms[id]?.numberOfLoops ?? 10)
}

self.mixOtherAudios()
Expand Down Expand Up @@ -388,7 +440,6 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
// Show notification on app kill
@objc func applicationWillTerminate(_ notification: Notification) {
scheduleImmediateNotification()
// scheduleDelayedNotification()
}

func scheduleImmediateNotification() {
Expand Down
71 changes: 71 additions & 0 deletions ios/Classes/services/NotificationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,74 @@ class NotificationManager {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["alarm-\(id)"])
}
}

extension NotificationManager {

/// Allows you to schedule local notifications
func programLocalNotif(nbrOfRepeat: Int = 10, duration: Int = 10,hour: Int, minute: Int, title: String, body: String, sound: UNNotificationSound?) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pas besoin d'avoir la doc sur les paramètres ici.

for indexOfNotif in 0..<nbrOfRepeat {
let request = UNNotificationRequest(identifier: "notif\(indexOfNotif)",
content: notifContentMaker(title: title, body: body, sound: sound),
trigger: triggerMaker(timeInterval: indexOfNotif, at: hour, minute, duration))

UNUserNotificationCenter.current().add(request)
}
}

func cancelNotificationBis(nbrOfRepeat: Int) {
for indexOfNotif in 0..<nbrOfRepeat {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notif\(indexOfNotif)"])
}
}

/// Sets & create the body of the notification.
private func notifContentMaker(title: String, body: String, sound: UNNotificationSound?) -> UNMutableNotificationContent {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = sound
return content
}

/// Turns a date into a trigger for notification
private func triggerMaker(timeInterval: Int, at hour: Int, _ minute: Int, _ duration: Int = 10) -> UNCalendarNotificationTrigger {
return UNCalendarNotificationTrigger(dateMatching: scheduleNotifications(timeInterval, hour, minute, duration), repeats: false)
}

/// This function schedules the notification time.
private func scheduleNotifications(_ add: Int,_ hour: Int,_ minute: Int,_ duration: Int = 10) -> DateComponents {
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = 0

let secondsToAdd = add * duration

let hoursToAdd = secondsToAdd / 3600
let minutesToAdd = (secondsToAdd % 3600) / 60
let secondsLeft = (secondsToAdd % 3600) % 60

dateComponents.hour? += hoursToAdd
dateComponents.minute? += minutesToAdd
dateComponents.second? += secondsLeft

if let minute = dateComponents.minute, minute >= 60 {
dateComponents.minute = minute % 60
dateComponents.hour? += minute / 60
}

if let second = dateComponents.second, second >= 60 {
dateComponents.second = second % 60
if let minute = dateComponents.minute {
dateComponents.minute = (minute + second / 60) % 60
if let hour = dateComponents.hour {
dateComponents.hour = hour + (minute + second / 60) / 60
}
}
}

return dateComponents
}


}
Loading