From 7caa0211005cd99ac57481c5c9c755178d5cae40 Mon Sep 17 00:00:00 2001 From: Orkun Duman Date: Sun, 8 Dec 2024 14:06:44 -0500 Subject: [PATCH] [iOS] Call AlarmTriggerApi for alarm events and use triggerTime when evaluating isRinging --- CHANGELOG.md | 4 ++ example/ios/Runner.xcodeproj/project.pbxproj | 6 +- example/pubspec.lock | 8 +-- ios/Classes/AlarmPlugin.h | 4 -- ios/Classes/AlarmPlugin.m | 15 ---- ios/Classes/SwiftAlarmPlugin.swift | 12 +++- ios/Classes/api/AlarmApiImpl.swift | 38 +++++++++-- lib/src/ios_alarm.dart | 72 -------------------- pubspec.yaml | 6 +- 9 files changed, 56 insertions(+), 109 deletions(-) delete mode 100644 ios/Classes/AlarmPlugin.h delete mode 100644 ios/Classes/AlarmPlugin.m diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c361392..07862e27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.1 +* Fixes a bug where `isRinging` might return FALSE immediately after alarm starts to ring. +* Handles alarm events on the platform side, increasing efficiency. + ## 5.0.0 * **BREAKING**: Old alarms (alarms created pre v5) will be deleted. * BREAKING: Some API parameters have been renamed, this update requires a small amount of refactoring for users. No features have been removed. diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a06ba510..f9809977 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -378,7 +378,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 63LD84R3KS; + DEVELOPMENT_TEAM = WQ65PJ26MP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -514,7 +514,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 63LD84R3KS; + DEVELOPMENT_TEAM = WQ65PJ26MP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -542,7 +542,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 63LD84R3KS; + DEVELOPMENT_TEAM = WQ65PJ26MP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/example/pubspec.lock b/example/pubspec.lock index 694801a6..c8b855fa 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -392,10 +392,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: @@ -408,10 +408,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: diff --git a/ios/Classes/AlarmPlugin.h b/ios/Classes/AlarmPlugin.h deleted file mode 100644 index e8728d92..00000000 --- a/ios/Classes/AlarmPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface AlarmPlugin : NSObject -@end diff --git a/ios/Classes/AlarmPlugin.m b/ios/Classes/AlarmPlugin.m deleted file mode 100644 index 293e9306..00000000 --- a/ios/Classes/AlarmPlugin.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "AlarmPlugin.h" -#if __has_include() -#import -#else -// Support project import fallback if the generated compatibility header -// is not copied when this plugin is created as a library. -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 -#import "alarm-Swift.h" -#endif - -@implementation AlarmPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftAlarmPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/ios/Classes/SwiftAlarmPlugin.swift b/ios/Classes/SwiftAlarmPlugin.swift index 68fe28db..5189f239 100644 --- a/ios/Classes/SwiftAlarmPlugin.swift +++ b/ios/Classes/SwiftAlarmPlugin.swift @@ -5,11 +5,19 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin { static let backgroundTaskIdentifier: String = "com.gdelataillade.fetch" private static var api: AlarmApiImpl? = nil - - public static func register(with registrar: FlutterPluginRegistrar) { + static var alarmTriggerApi: AlarmTriggerApi? = nil + + public static func register(with registrar: any FlutterPluginRegistrar) { self.api = AlarmApiImpl(registrar: registrar) AlarmApiSetup.setUp(binaryMessenger: registrar.messenger(), api: self.api) NSLog("[SwiftAlarmPlugin] AlarmApi initialized.") + self.alarmTriggerApi = AlarmTriggerApi(binaryMessenger: registrar.messenger()) + NSLog("[SwiftAlarmPlugin] AlarmTriggerApi initialized.") + } + + public func detachFromEngine(for registrar: any FlutterPluginRegistrar) { + SwiftAlarmPlugin.alarmTriggerApi = nil + NSLog("[SwiftAlarmPlugin] AlarmTriggerApi detached.") } public func applicationWillTerminate(_ application: UIApplication) { diff --git a/ios/Classes/api/AlarmApiImpl.swift b/ios/Classes/api/AlarmApiImpl.swift index 9cf7480e..55f9912f 100644 --- a/ios/Classes/api/AlarmApiImpl.swift +++ b/ios/Classes/api/AlarmApiImpl.swift @@ -1,4 +1,5 @@ import AVFoundation +import Flutter import MediaPlayer public class AlarmApiImpl: NSObject, AlarmApi { @@ -62,6 +63,7 @@ public class AlarmApiImpl: NSObject, AlarmApi { let currentTime = audioPlayer.deviceCurrentTime let time = currentTime + delayInSeconds let dateTime = Date().addingTimeInterval(delayInSeconds) + self.alarms[id]?.triggerTime = dateTime if alarmSettings.loopAudio { audioPlayer.numberOfLoops = -1 @@ -77,7 +79,6 @@ public class AlarmApiImpl: NSObject, AlarmApi { audioPlayer.play(atTime: time + 0.5) self.alarms[id]?.audioPlayer = audioPlayer - self.alarms[id]?.triggerTime = dateTime self.alarms[id]?.task = DispatchWorkItem(block: { self.handleAlarmAfterDelay(id: id) }) @@ -96,9 +97,7 @@ public class AlarmApiImpl: NSObject, AlarmApi { func isRinging(alarmId: Int64?) throws -> Bool { if let alarmId = alarmId { let id = Int(truncatingIfNeeded: alarmId) - let isPlaying = self.alarms[id]?.audioPlayer?.isPlaying ?? false - let currentTime = self.alarms[id]?.audioPlayer?.currentTime ?? 0.0 - return isPlaying && currentTime > 0 + return self.alarms[id]?.triggerTime?.timeIntervalSinceNow ?? 1.0 <= 0.0 } else { return self.isAnyAlarmRinging() } @@ -226,7 +225,16 @@ public class AlarmApiImpl: NSObject, AlarmApi { private func isAnyAlarmRinging() -> Bool { for (_, alarmConfig) in self.alarms { - if let audioPlayer = alarmConfig.audioPlayer, audioPlayer.isPlaying, audioPlayer.currentTime > 0 { + if alarmConfig.triggerTime?.timeIntervalSinceNow ?? 1.0 <= 0.0 { + return true + } + } + return false + } + + private func isAnyAlarmRingingExcept(id: Int) -> Bool { + for (alarmId, alarmConfig) in self.alarms { + if alarmId != id && alarmConfig.triggerTime?.timeIntervalSinceNow ?? 1.0 <= 0.0 { return true } } @@ -234,7 +242,7 @@ public class AlarmApiImpl: NSObject, AlarmApi { } private func handleAlarmAfterDelay(id: Int) { - if self.isAnyAlarmRinging() { + if self.isAnyAlarmRingingExcept(id: id) { NSLog("[SwiftAlarmPlugin] Ignoring alarm with id \(id) because another alarm is already ringing.") self.unsaveAlarm(id: id) return @@ -249,6 +257,15 @@ public class AlarmApiImpl: NSObject, AlarmApi { if !audioPlayer.isPlaying || audioPlayer.currentTime == 0.0 { audioPlayer.play() } + + // Inform the Flutter plugin that the alarm rang + SwiftAlarmPlugin.alarmTriggerApi?.alarmRang(alarmId: Int64(id), completion: { result in + if case .success = result { + NSLog("[SwiftAlarmPlugin] Alarm rang notification for \(id) was processed successfully by Flutter.") + } else { + NSLog("[SwiftAlarmPlugin] Alarm rang notification for \(id) encountered error in Flutter.") + } + }) if alarm.settings.vibrate { self.vibratingAlarms.insert(id) @@ -375,6 +392,15 @@ public class AlarmApiImpl: NSObject, AlarmApi { self.stopSilentSound() self.stopNotificationOnKillService() + + // Inform the Flutter plugin that the alarm was stopped + SwiftAlarmPlugin.alarmTriggerApi?.alarmStopped(alarmId: Int64(id), completion: { result in + if case .success = result { + NSLog("[SwiftAlarmPlugin] Alarm stopped notification for \(id) was processed successfully by Flutter.") + } else { + NSLog("[SwiftAlarmPlugin] Alarm stopped notification for \(id) encountered error in Flutter.") + } + }) } private func stopSilentSound() { diff --git a/lib/src/ios_alarm.dart b/lib/src/ios_alarm.dart index b3a26f4e..dee230ca 100644 --- a/lib/src/ios_alarm.dart +++ b/lib/src/ios_alarm.dart @@ -5,18 +5,11 @@ import 'package:alarm/src/generated/platform_bindings.g.dart'; import 'package:alarm/utils/alarm_exception.dart'; import 'package:alarm/utils/alarm_handler.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_fgbg/flutter_fgbg.dart'; /// Uses method channel to interact with the native platform. class IOSAlarm { static final AlarmApi _api = AlarmApi(); - /// Map of alarm timers. - static Map timers = {}; - - /// Map of foreground/background subscriptions. - static Map?> fgbgSubscriptions = {}; - /// Calls the native function `setAlarm` and listens to alarm ring state. /// /// Also set periodic timer and listens for app state changes to trigger @@ -35,42 +28,12 @@ class IOSAlarm { rethrow; } - if (timers[id] != null && timers[id]!.isActive) timers[id]!.cancel(); - timers[id] = periodicTimer( - () => Alarm.ringStream.add(settings), - settings.dateTime, - id, - ); - - listenAppStateChange( - id: id, - onBackground: () => disposeTimer(id), - onForeground: () async { - if (fgbgSubscriptions[id] == null) return; - - final alarmIsRinging = await isRinging(id); - - if (alarmIsRinging) { - disposeAlarm(id); - Alarm.ringStream.add(settings); - } else { - if (timers[id] != null && timers[id]!.isActive) timers[id]!.cancel(); - timers[id] = periodicTimer( - () => Alarm.ringStream.add(settings), - settings.dateTime, - id, - ); - } - }, - ); - return true; } /// Disposes timer and FGBG subscription /// and calls the native `stopAlarm` function. static Future stopAlarm(int id) async { - disposeAlarm(id); try { await _api .stopAlarm(alarmId: id) @@ -96,44 +59,9 @@ class IOSAlarm { } } - /// Listens when app goes foreground so we can check if alarm is ringing. - /// When app goes background, periodical timer will be disposed. - static void listenAppStateChange({ - required int id, - required void Function() onForeground, - required void Function() onBackground, - }) { - fgbgSubscriptions[id] = FGBGEvents.instance.stream.listen((event) { - if (event == FGBGType.foreground) onForeground(); - if (event == FGBGType.background) onBackground(); - }); - } - - /// Checks periodically if alarm is ringing, as long as app is in foreground. - static Timer periodicTimer(void Function()? onRing, DateTime dt, int id) { - return Timer.periodic(const Duration(milliseconds: 200), (_) { - if (DateTime.now().isBefore(dt)) return; - disposeAlarm(id); - onRing?.call(); - }); - } - /// Sets the native notification on app kill title and body. static Future setWarningNotificationOnKill(String title, String body) => _api .setWarningNotificationOnKill(title: title, body: body) .catchError(AlarmExceptionHandlers.catchError); - - /// Disposes alarm timer. - static void disposeTimer(int id) { - timers[id]?.cancel(); - timers.removeWhere((key, value) => key == id); - } - - /// Disposes alarm timer and FGBG subscription. - static void disposeAlarm(int id) { - disposeTimer(id); - fgbgSubscriptions[id]?.cancel(); - fgbgSubscriptions.removeWhere((key, value) => key == id); - } } diff --git a/pubspec.yaml b/pubspec.yaml index bdb8803e..4fa3b79c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: alarm description: A simple Flutter alarm manager plugin for both iOS and Android. -version: 4.1.1 +version: 5.0.1 homepage: https://github.com/gdelataillade/alarm environment: @@ -21,7 +21,7 @@ dev_dependencies: flutter_test: sdk: flutter json_serializable: ^6.9.0 - pigeon: ^22.6.3 + pigeon: ^22.7.0 very_good_analysis: ^6.0.0 flutter: @@ -35,4 +35,4 @@ flutter: package: com.gdelataillade.alarm.alarm pluginClass: AlarmPlugin ios: - pluginClass: AlarmPlugin \ No newline at end of file + pluginClass: SwiftAlarmPlugin \ No newline at end of file