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

Implement stopAll natively #301

Merged
merged 2 commits into from
Dec 11, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Add support for fading the alarm volume using a staircase function.
* Fixes a bug where `isRinging` might return FALSE immediately after alarm starts to ring.
* Handles alarm events on the platform side, increasing efficiency.
* Handles `stopAll` on the platform side for improved reliability.

## 4.1.1
* [Android] Show app on lock screen when alarm rings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ class AlarmApiImpl(private val context: Context) : AlarmApi {
}
}

override fun stopAll() {
for (alarm in AlarmStorage(context).getSavedAlarms()) {
stopAlarm(alarm.id.toLong())
}
val alarmIdsCopy = alarmIds.toList()
for (alarmId in alarmIdsCopy) {
stopAlarm(alarmId.toLong())
}
}

override fun isRinging(alarmId: Long?): Boolean {
val ringingAlarmIds = AlarmService.ringingAlarmIds
if (alarmId == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ private open class FlutterBindingsPigeonCodec : StandardMessageCodec() {
interface AlarmApi {
fun setAlarm(alarmSettings: AlarmSettingsWire)
fun stopAlarm(alarmId: Long)
fun stopAll()
fun isRinging(alarmId: Long?): Boolean
fun setWarningNotificationOnKill(title: String, body: String)
fun disableWarningNotificationOnKill()
Expand Down Expand Up @@ -300,6 +301,22 @@ interface AlarmApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.alarm.AlarmApi.stopAll$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.stopAll()
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.alarm.AlarmApi.isRinging$separatedMessageChannelSuffix", codec)
if (api != null) {
Expand Down
6 changes: 6 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ PODS:
- Flutter (1.0.0)
- flutter_fgbg (0.0.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
- shared_preferences_foundation (0.0.1):
Expand All @@ -16,6 +18,7 @@ DEPENDENCIES:
- alarm (from `.symlinks/plugins/alarm/ios`)
- Flutter (from `Flutter`)
- flutter_fgbg (from `.symlinks/plugins/flutter_fgbg/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
Expand All @@ -27,6 +30,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_fgbg:
:path: ".symlinks/plugins/flutter_fgbg/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation:
Expand All @@ -38,6 +43,7 @@ SPEC CHECKSUMS:
alarm: 6c1f6a9688f94cd6bf8f104c67cc26e78c9d8d13
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_fgbg: 31c0d1140a131daea2d342121808f6aa0dcd879d
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
Expand Down
10 changes: 10 additions & 0 deletions example/lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ class _ExampleAlarmHomeScreenState extends State<ExampleAlarmHomeScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ExampleAlarmHomeShortcutButton(refreshAlarms: loadAlarms),
const FloatingActionButton(
onPressed: Alarm.stopAll,
backgroundColor: Colors.red,
heroTag: null,
child: Text(
'STOP ALL',
textScaler: TextScaler.linear(0.9),
textAlign: TextAlign.center,
),
),
FloatingActionButton(
onPressed: () => navigateToAlarmScreen(null),
child: const Icon(Icons.alarm_add_rounded, size: 33),
Expand Down
8 changes: 6 additions & 2 deletions example/lib/screens/shortcut_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@ class _ExampleAlarmHomeShortcutButtonState
},
child: FloatingActionButton(
onPressed: () => onPressButton(0),
backgroundColor: Colors.red,
backgroundColor: Colors.green[700],
heroTag: null,
child: const Text('RING NOW', textAlign: TextAlign.center),
child: const Text(
'RING NOW',
textScaler: TextScaler.linear(0.9),
textAlign: TextAlign.center,
),
),
),
if (showMenu)
Expand Down
36 changes: 36 additions & 0 deletions ios/Classes/api/AlarmApiImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,42 @@ public class AlarmApiImpl: NSObject, AlarmApi {
func stopAlarm(alarmId: Int64) throws {
self.stopAlarmInternal(id: Int(truncatingIfNeeded: alarmId), cancelNotif: true)
}

func stopAll() throws {
NotificationManager.shared.removeAllNotifications()

self.mixOtherAudios()

self.vibratingAlarms.removeAll()

if let previousVolume = self.previousVolume {
self.setVolume(volume: previousVolume, enable: false)
}

let alarmIds = self.alarms.keys

for (_, alarm) in self.alarms {
alarm.timer?.invalidate()
alarm.task?.cancel()
alarm.audioPlayer?.stop()
alarm.volumeEnforcementTimer?.invalidate()
}
self.alarms.removeAll()

self.stopSilentSound()
self.stopNotificationOnKillService()

for (id) in alarmIds {
// 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.")
}
})
}
}

func isRinging(alarmId: Int64?) throws -> Bool {
if let alarmId = alarmId {
Expand Down
16 changes: 14 additions & 2 deletions ios/Classes/generated/FlutterBindings.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ struct VolumeSettingsWire {
var volumeEnforced: Bool



// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> VolumeSettingsWire? {
let volume: Double? = nilOrValue(pigeonVar_list[0])
Expand Down Expand Up @@ -175,7 +174,6 @@ struct VolumeFadeStepWire {
var volume: Double



// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> VolumeFadeStepWire? {
let timeMillis = pigeonVar_list[0] as! Int64
Expand Down Expand Up @@ -290,6 +288,7 @@ class FlutterBindingsPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendab
protocol AlarmApi {
func setAlarm(alarmSettings: AlarmSettingsWire) throws
func stopAlarm(alarmId: Int64) throws
func stopAll() throws
func isRinging(alarmId: Int64?) throws -> Bool
func setWarningNotificationOnKill(title: String, body: String) throws
func disableWarningNotificationOnKill() throws
Expand Down Expand Up @@ -331,6 +330,19 @@ class AlarmApiSetup {
} else {
stopAlarmChannel.setMessageHandler(nil)
}
let stopAllChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.alarm.AlarmApi.stopAll\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
stopAllChannel.setMessageHandler { _, reply in
do {
try api.stopAll()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
stopAllChannel.setMessageHandler(nil)
}
let isRingingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.alarm.AlarmApi.isRinging\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
isRingingChannel.setMessageHandler { message, reply in
Expand Down
5 changes: 5 additions & 0 deletions ios/Classes/services/NotificationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
let notificationIdentifier = "alarm-\(id)"
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationIdentifier])
}

func removeAllNotifications() {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
}

func handleAction(withIdentifier identifier: String?, for notification: UNNotification) {
guard let identifier = identifier else { return }
Expand Down
6 changes: 5 additions & 1 deletion lib/alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,12 @@ class Alarm {
static Future<void> stopAll() async {
final alarms = await getAlarms();

iOS ? await IOSAlarm.stopAll() : await AndroidAlarm.stopAll();

await AlarmStorage.unsaveAll();

for (final alarm in alarms) {
await stop(alarm.id);
updateStream.add(alarm.id);
}
}

Expand Down
12 changes: 12 additions & 0 deletions lib/service/alarm_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ class AlarmStorage {
await _prefs.remove('$prefix$id');
}

/// Removes all alarms from local storage.
static Future<void> unsaveAll() async {
await _waitUntilInitialized();

final keys = _prefs.getKeys();
for (final key in keys) {
if (key.startsWith(prefix)) {
await _prefs.remove(key);
}
}
}

/// Whether at least one alarm is set.
static Future<bool> hasAlarm() async {
await _waitUntilInitialized();
Expand Down
5 changes: 5 additions & 0 deletions lib/src/android_alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class AndroidAlarm {
}
}

/// Calls the native `stopAll` function.
static Future<void> stopAll() async {
return _api.stopAll().catchError(AlarmExceptionHandlers.catchError<void>);
}

/// Checks whether an alarm or any alarm (if id is null) is ringing.
static Future<bool> isRinging([int? id]) async {
try {
Expand Down
24 changes: 24 additions & 0 deletions lib/src/generated/platform_bindings.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,30 @@ class AlarmApi {
}
}

Future<void> stopAll() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.alarm.AlarmApi.stopAll$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(null) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}

Future<bool> isRinging({required int? alarmId}) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.alarm.AlarmApi.isRinging$pigeonVar_messageChannelSuffix';
Expand Down
6 changes: 5 additions & 1 deletion lib/src/ios_alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class IOSAlarm {
return true;
}

/// Disposes timer and FGBG subscription
/// and calls the native `stopAlarm` function.
static Future<bool> stopAlarm(int id) async {
try {
Expand All @@ -46,6 +45,11 @@ class IOSAlarm {
}
}

/// Calls the native `stopAll` function.
static Future<void> stopAll() async {
return _api.stopAll().catchError(AlarmExceptionHandlers.catchError<void>);
}

/// Checks whether an alarm or any alarm (if id is null) is ringing.
static Future<bool> isRinging([int? id]) async {
try {
Expand Down
2 changes: 2 additions & 0 deletions pigeons/alarm_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ abstract class AlarmApi {

void stopAlarm({required int alarmId});

void stopAll();

bool isRinging({required int? alarmId});

void setWarningNotificationOnKill({
Expand Down