From 77095680ab88c88cb5ae8ebe86c738798587d2a4 Mon Sep 17 00:00:00 2001 From: Gautier de Lataillade <32983806+gdelataillade@users.noreply.github.com> Date: Sat, 18 Nov 2023 18:33:54 +0100 Subject: [PATCH] Set volume to value between 0 and 1 (#98) * Set volume property as double * Update volume value to iOS and Android methods * Add volume slider in example app * Fix iOS volume argument name * Fix iOS build errors * Fix iOS volume type error * Update changelog + add minor adjustments --- CHANGELOG.md | 1 + README.md | 21 +++---- .../gdelataillade/alarm/alarm/AlarmService.kt | 2 +- example/lib/main.dart | 7 ++- example/lib/screens/edit_alarm.dart | 60 +++++++++++++------ example/lib/screens/shortcut_button.dart | 4 +- example/pubspec.lock | 18 +++--- ios/Classes/SwiftAlarmPlugin.swift | 25 ++++---- lib/model/alarm_settings.dart | 33 +++++----- lib/src/android_alarm.dart | 2 +- lib/src/ios_alarm.dart | 2 +- 11 files changed, 109 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c11f2816..67067ab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Remove [stopOnNotificationOpen] property. * Make notification mandatory so android foreground services can be used. * [Android] Refactor alarm to native android services. +* Replace [volumeMax] with [volume] double property. ## 2.2.0 * [Android] Move alarm service to native code. diff --git a/README.md b/README.md index 819965b6..3b461e58 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ final alarmSettings = AlarmSettings( assetAudioPath: 'assets/alarm.mp3', loopAudio: true, vibrate: true, - volumeMax: true, + volume: 0.8, fadeDuration: 3.0, notificationTitle: 'This is the title', notificationBody: 'This is the body', @@ -58,7 +58,8 @@ alarmDateTime | `DateTime` | The date and time you want your alarm to ring assetAudio | `String` | The path to you audio asset you want to use as ringtone. Can be a path in your assets folder or a downloaded local file path. loopAudio | `bool` | If true, audio will repeat indefinitely until alarm is stopped. vibrate | `bool` | If true, device will vibrate indefinitely until alarm is stopped. If [loopAudio] is set to false, vibrations will stop when audio ends. -volumeMax | `bool` | If true, set system volume to maximum when [dateTime] is reached. Set back to previous volume when alarm is stopped. +volume | `double` | Sets system volume level (0 to 1) at [dateTime]; reverts on alarm stop. Defaults to current volume if null. +when alarm is stopped. fadeDuration | `double` | Duration, in seconds, over which to fade the alarm volume. Set to 0 by default, which means no fade. notificationTitle | `String` | The title of the notification triggered when alarm rings if app is on background. notificationBody | `String` | The body of the notification. @@ -91,14 +92,14 @@ Don't hesitate to check out the example's code, and take a look at the app: ## ⏰ Alarm behaviour -| | Sound | Vibrate | Volume max | Notification -| ------------------------ | ----- | ------- | ---------- | ------- -| Locked screen | ✅ | ✅ | ✅ | ✅ -| Silent / Mute | ✅ | ✅ | ✅ | ✅ -| Do not disturb | ✅ | ✅ | ✅ | Silenced -| Sleep mode | ✅ | ✅ | ✅ | Silenced -| While playing other media| ✅ | ✅ | ✅ | ✅ -| App killed | 🤖 | 🤖 | 🤖 | ✅ +| | Sound | Vibrate | Volume | Notification +| ------------------------ | ----- | ------- | -------| ------- +| Locked screen | ✅ | ✅ | ✅ | ✅ +| Silent / Mute | ✅ | ✅ | ✅ | ✅ +| Do not disturb | ✅ | ✅ | ✅ | Silenced +| Sleep mode | ✅ | ✅ | ✅ | Silenced +| While playing other media| ✅ | ✅ | ✅ | ✅ +| App killed | 🤖 | 🤖 | 🤖 | ✅ ✅ : iOS and Android 🤖 : Android only. diff --git a/android/src/main/kotlin/com/gdelataillade/alarm/alarm/AlarmService.kt b/android/src/main/kotlin/com/gdelataillade/alarm/alarm/AlarmService.kt index 9d4439a1..0e0bed98 100644 --- a/android/src/main/kotlin/com/gdelataillade/alarm/alarm/AlarmService.kt +++ b/android/src/main/kotlin/com/gdelataillade/alarm/alarm/AlarmService.kt @@ -89,7 +89,7 @@ class AlarmService : Service() { Log.d("AlarmService", "Error while invoking alarmRinging channel: $e") } - if (volume != -1.0) { + if (volume >= 0.0 && volume <= 1.0) { volumeService?.setVolume(volume, showSystemUI) } diff --git a/example/lib/main.dart b/example/lib/main.dart index 61dc4768..fe21cda6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -10,5 +10,10 @@ Future main() async { await Alarm.init(showDebugLogs: true); - runApp(const MaterialApp(home: ExampleAlarmHomeScreen())); + runApp( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const ExampleAlarmHomeScreen(), + ), + ); } diff --git a/example/lib/screens/edit_alarm.dart b/example/lib/screens/edit_alarm.dart index c22e5c9f..69b9f902 100644 --- a/example/lib/screens/edit_alarm.dart +++ b/example/lib/screens/edit_alarm.dart @@ -18,7 +18,7 @@ class _ExampleAlarmEditScreenState extends State { late DateTime selectedDateTime; late bool loopAudio; late bool vibrate; - late bool volumeMax; + late double? volume; late String assetAudio; @override @@ -31,13 +31,13 @@ class _ExampleAlarmEditScreenState extends State { selectedDateTime = selectedDateTime.copyWith(second: 0, millisecond: 0); loopAudio = true; vibrate = true; - volumeMax = false; + volume = null; assetAudio = 'assets/marimba.mp3'; } else { selectedDateTime = widget.alarmSettings!.dateTime; loopAudio = widget.alarmSettings!.loopAudio; vibrate = widget.alarmSettings!.vibrate; - volumeMax = widget.alarmSettings!.volumeMax; + volume = widget.alarmSettings!.volume; assetAudio = widget.alarmSettings!.assetAudioPath; } } @@ -87,7 +87,7 @@ class _ExampleAlarmEditScreenState extends State { dateTime: selectedDateTime, loopAudio: loopAudio, vibrate: vibrate, - volumeMax: volumeMax, + volume: volume, assetAudioPath: assetAudio, notificationTitle: 'Alarm example', notificationBody: 'Your alarm ($id) is ringing', @@ -190,19 +190,6 @@ class _ExampleAlarmEditScreenState extends State { ), ], ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'System volume max', - style: Theme.of(context).textTheme.titleMedium, - ), - Switch( - value: volumeMax, - onChanged: (value) => setState(() => volumeMax = value), - ), - ], - ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -238,6 +225,45 @@ class _ExampleAlarmEditScreenState extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Custom volume', + style: Theme.of(context).textTheme.titleMedium, + ), + Switch( + value: volume != null, + onChanged: (value) => + setState(() => volume = value ? 0.5 : null), + ), + ], + ), + SizedBox( + height: 30, + child: volume != null + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon( + volume! > 0.7 + ? Icons.volume_up_rounded + : volume! > 0.1 + ? Icons.volume_down_rounded + : Icons.volume_mute_rounded, + ), + Expanded( + child: Slider( + value: volume!, + onChanged: (value) { + setState(() => volume = value); + }, + ), + ), + ], + ) + : const SizedBox(), + ), if (!creating) TextButton( onPressed: deleteAlarm, diff --git a/example/lib/screens/shortcut_button.dart b/example/lib/screens/shortcut_button.dart index 7bcfccc9..fc83c99d 100644 --- a/example/lib/screens/shortcut_button.dart +++ b/example/lib/screens/shortcut_button.dart @@ -18,9 +18,11 @@ class _ExampleAlarmHomeShortcutButtonState Future onPressButton(int delayInHours) async { DateTime dateTime = DateTime.now().add(Duration(hours: delayInHours)); + double? volume; if (delayInHours != 0) { dateTime = dateTime.copyWith(second: 0, millisecond: 0); + volume = 0.5; } setState(() => showMenu = false); @@ -29,7 +31,7 @@ class _ExampleAlarmHomeShortcutButtonState id: DateTime.now().millisecondsSinceEpoch % 10000, dateTime: dateTime, assetAudioPath: 'assets/marimba.mp3', - volumeMax: false, + volume: volume, notificationTitle: 'Alarm example', notificationBody: 'Shortcut button alarm with delay of $delayInHours hours', diff --git a/example/pubspec.lock b/example/pubspec.lock index 365475d2..2e105762 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: path: ".." relative: true source: path - version: "2.2.0" + version: "3.0.0-dev.1" args: dependency: transitive description: @@ -68,10 +68,10 @@ packages: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" + version: "0.7.10" fake_async: dependency: transitive description: @@ -235,10 +235,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" shared_preferences: dependency: transitive description: @@ -283,10 +283,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_windows: dependency: transitive description: @@ -397,5 +397,5 @@ packages: source: hosted version: "6.4.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/ios/Classes/SwiftAlarmPlugin.swift b/ios/Classes/SwiftAlarmPlugin.swift index e561385b..b2eb9109 100644 --- a/ios/Classes/SwiftAlarmPlugin.swift +++ b/ios/Classes/SwiftAlarmPlugin.swift @@ -84,8 +84,13 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin { let loopAudio = args["loopAudio"] as! Bool let fadeDuration = args["fadeDuration"] as! Double let vibrationsEnabled = args["vibrate"] as! Bool - let volumeMax = args["volumeMax"] as! Bool + let volume = args["volume"] as? Double let assetAudio = args["assetAudio"] as! String + + var volumeFloat: Float? = nil + if let volumeValue = volume { + volumeFloat = Float(volumeValue) + } if assetAudio.hasPrefix("assets/") { let filename = registrar.lookupKey(forAsset: assetAudio) @@ -146,7 +151,7 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin { fadeDuration: fadeDuration, vibrationsEnabled: vibrationsEnabled, audioLoop: loopAudio, - volumeMax: volumeMax + volume: volumeFloat ) }) @@ -214,7 +219,7 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin { } } - private func handleAlarmAfterDelay(id: Int, triggerTime: Date, fadeDuration: Double, vibrationsEnabled: Bool, audioLoop: Bool, volumeMax: Bool) { + private func handleAlarmAfterDelay(id: Int, triggerTime: Date, fadeDuration: Double, vibrationsEnabled: Bool, audioLoop: Bool, volume: Float?) { guard let audioPlayer = self.audioPlayers[id], let storedTriggerTime = triggerTimes[id], triggerTime == storedTriggerTime else { return } @@ -234,14 +239,12 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin { self.vibrate = false } } - - if fadeDuration > 0.0 { - if volumeMax { - self.setVolume(volume: 1.0, enable: true) - } - audioPlayer.setVolume(1.0, fadeDuration: fadeDuration) - } else if volumeMax { - self.setVolume(volume: 1.0, enable: true) + + if let volumeValue = volume { + self.setVolume(volume: volumeValue, enable: true) + } + if fadeDuration > 0.0 { + audioPlayer.setVolume(1.0, fadeDuration: fadeDuration) } } diff --git a/lib/model/alarm_settings.dart b/lib/model/alarm_settings.dart index 7d621a2e..a9363cbd 100644 --- a/lib/model/alarm_settings.dart +++ b/lib/model/alarm_settings.dart @@ -20,10 +20,15 @@ class AlarmSettings { /// If [loopAudio] is set to false, vibrations will stop when audio ends. final bool vibrate; - /// If true, set system volume to maximum when [dateTime] is reached - /// and set it back to its previous value when alarm is stopped. - /// Else, use current system volume. Enabled by default. - final bool volumeMax; + /// Specifies the system volume level to be set at the designated [dateTime]. + /// + /// Accepts a value between 0 (mute) and 1 (maximum volume). When the alarm is triggered at [dateTime], + /// the system volume adjusts to this specified level. Upon stopping the alarm, the system volume reverts + /// to its prior setting. + /// + /// If left unspecified or set to `null`, the current system volume at the time of the alarm will be used. + /// Defaults to `null`. + final double? volume; /// Duration, in seconds, over which to fade the alarm ringtone. /// Set to 0.0 by default, which means no fade. @@ -52,7 +57,7 @@ class AlarmSettings { hash = hash ^ assetAudioPath.hashCode; hash = hash ^ loopAudio.hashCode; hash = hash ^ vibrate.hashCode; - hash = hash ^ volumeMax.hashCode; + hash = hash ^ volume.hashCode; hash = hash ^ fadeDuration.hashCode; hash = hash ^ (notificationTitle.hashCode); hash = hash ^ (notificationBody.hashCode); @@ -73,7 +78,7 @@ class AlarmSettings { required this.assetAudioPath, this.loopAudio = true, this.vibrate = true, - this.volumeMax = true, + this.volume, this.fadeDuration = 0.0, required this.notificationTitle, required this.notificationBody, @@ -87,11 +92,11 @@ class AlarmSettings { dateTime: DateTime.fromMicrosecondsSinceEpoch(json['dateTime'] as int), assetAudioPath: json['assetAudioPath'] as String, loopAudio: json['loopAudio'] as bool, - vibrate: json['vibrate'] as bool, - volumeMax: json['volumeMax'] as bool, + vibrate: json['vibrate'] as bool? ?? true, + volume: json['volume'] as double?, fadeDuration: json['fadeDuration'] as double, - notificationTitle: json['notificationTitle'] as String, - notificationBody: json['notificationBody'] as String, + notificationTitle: json['notificationTitle'] as String? ?? '', + notificationBody: json['notificationBody'] as String? ?? '', enableNotificationOnKill: json['enableNotificationOnKill'] as bool? ?? true, androidFullScreenIntent: @@ -106,7 +111,7 @@ class AlarmSettings { String? assetAudioPath, bool? loopAudio, bool? vibrate, - bool? volumeMax, + double? volume, double? fadeDuration, String? notificationTitle, String? notificationBody, @@ -119,7 +124,7 @@ class AlarmSettings { assetAudioPath: assetAudioPath ?? this.assetAudioPath, loopAudio: loopAudio ?? this.loopAudio, vibrate: vibrate ?? this.vibrate, - volumeMax: volumeMax ?? this.volumeMax, + volume: volume ?? this.volume, fadeDuration: fadeDuration ?? this.fadeDuration, notificationTitle: notificationTitle ?? this.notificationTitle, notificationBody: notificationBody ?? this.notificationBody, @@ -137,7 +142,7 @@ class AlarmSettings { 'assetAudioPath': assetAudioPath, 'loopAudio': loopAudio, 'vibrate': vibrate, - 'volumeMax': volumeMax, + 'volume': volume, 'fadeDuration': fadeDuration, 'notificationTitle': notificationTitle, 'notificationBody': notificationBody, @@ -165,7 +170,7 @@ class AlarmSettings { assetAudioPath == other.assetAudioPath && loopAudio == other.loopAudio && vibrate == other.vibrate && - volumeMax == other.volumeMax && + volume == other.volume && fadeDuration == other.fadeDuration && notificationTitle == other.notificationTitle && notificationBody == other.notificationBody && diff --git a/lib/src/android_alarm.dart b/lib/src/android_alarm.dart index ae2bf225..ae13342e 100644 --- a/lib/src/android_alarm.dart +++ b/lib/src/android_alarm.dart @@ -41,7 +41,7 @@ class AndroidAlarm { 'assetAudioPath': settings.assetAudioPath, 'loopAudio': settings.loopAudio, 'vibrate': settings.vibrate, - 'volume': settings.volumeMax ? 1.0 : -1.0, + 'volume': settings.volume, 'fadeDuration': settings.fadeDuration, 'notificationTitle': settings.notificationTitle, 'notificationBody': settings.notificationBody, diff --git a/lib/src/ios_alarm.dart b/lib/src/ios_alarm.dart index c8757c80..8b52cb18 100644 --- a/lib/src/ios_alarm.dart +++ b/lib/src/ios_alarm.dart @@ -34,7 +34,7 @@ class IOSAlarm { 'fadeDuration': settings.fadeDuration >= 0 ? settings.fadeDuration : 0, 'vibrate': settings.vibrate, - 'volumeMax': settings.volumeMax, + 'volume': settings.volume, 'notifOnKillEnabled': settings.enableNotificationOnKill, 'notifTitleOnAppKill': AlarmStorage.getNotificationOnAppKillTitle(),