Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
longtn-imt authored Mar 11, 2024
2 parents 62af8c1 + 1bd914a commit 2744961
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 196 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
![Pub Popularity](https://img.shields.io/pub/popularity/alarm)

[![alarm](https://github.com/gdelataillade/alarm/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/gdelataillade/alarm/actions/workflows/main.yml)
[![style: very good analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis)
[![GitHub Sponsor](https://img.shields.io/github/sponsors/gdelataillade?label=Sponsor&logo=GitHub)](https://github.com/sponsors/gdelataillade)

🏆 Winner of the [2023 OnePub Community Choice Awards](https://onepub.dev/Competition).
Expand Down
5 changes: 1 addition & 4 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
include: package:flutter_lints/flutter.yaml

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
include: package:very_good_analysis/analysis_options.yaml
1 change: 1 addition & 0 deletions example/lib/screens/edit_alarm.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:alarm/alarm.dart';
import 'package:alarm/model/alarm_settings.dart';
import 'package:flutter/material.dart';

class ExampleAlarmEditScreen extends StatefulWidget {
Expand Down
1 change: 1 addition & 0 deletions example/lib/screens/home.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:alarm/alarm.dart';
import 'package:alarm/model/alarm_settings.dart';
import 'package:alarm_example/screens/edit_alarm.dart';
import 'package:alarm_example/screens/ring.dart';
import 'package:alarm_example/screens/shortcut_button.dart';
Expand Down
1 change: 1 addition & 0 deletions example/lib/screens/ring.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:alarm/alarm.dart';
import 'package:alarm/model/alarm_settings.dart';
import 'package:flutter/material.dart';

class ExampleAlarmRingScreen extends StatelessWidget {
Expand Down
1 change: 1 addition & 0 deletions example/lib/screens/shortcut_button.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:alarm/alarm.dart';
import 'package:alarm/model/alarm_settings.dart';
import 'package:flutter/material.dart';

class ExampleAlarmHomeShortcutButton extends StatefulWidget {
Expand Down
23 changes: 23 additions & 0 deletions ios/Classes/AlarmConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import AVFoundation

class AlarmConfiguration {
let id: Int
let assetAudio: String
let vibrationsEnabled: Bool
let loopAudio: Bool
let fadeDuration: Double
let volume: Float?
var triggerTime: Date?
var audioPlayer: AVAudioPlayer?
var timer: Timer?
var task: DispatchWorkItem?

init(id: Int, assetAudio: String, vibrationsEnabled: Bool, loopAudio: Bool, fadeDuration: Double, volume: Float?) {
self.id = id
self.assetAudio = assetAudio
self.vibrationsEnabled = vibrationsEnabled
self.loopAudio = loopAudio
self.fadeDuration = fadeDuration
self.volume = volume
}
}
210 changes: 113 additions & 97 deletions ios/Classes/SwiftAlarmPlugin.swift

Large diffs are not rendered by default.

45 changes: 15 additions & 30 deletions lib/alarm.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// ignore_for_file: avoid_print

export 'package:alarm/model/alarm_settings.dart';
import 'dart:async';

import 'package:alarm/model/alarm_settings.dart';
import 'package:alarm/src/ios_alarm.dart';
import 'package:alarm/service/alarm_storage.dart';
import 'package:alarm/src/android_alarm.dart';
import 'package:alarm/service/storage.dart';
import 'package:alarm/src/ios_alarm.dart';
import 'package:alarm/utils/alarm_exception.dart';
import 'package:alarm/utils/extensions.dart';
import 'package:flutter/foundation.dart';

/// Custom print function designed for Alarm plugin.
DebugPrintCallback alarmPrint = debugPrintThrottled;

/// Class that handles the alarm.
class Alarm {
/// Whether it's iOS device.
static bool get iOS => defaultTargetPlatform == TargetPlatform.iOS;
Expand All @@ -31,7 +33,7 @@ class Alarm {
static Future<void> init({bool showDebugLogs = true}) async {
alarmPrint = (String? message, {int? wrapWidth}) {
if (kDebugMode && showDebugLogs) {
print("[Alarm] $message");
print('[Alarm] $message');
}
};

Expand Down Expand Up @@ -62,7 +64,7 @@ class Alarm {

/// Schedules an alarm with given [alarmSettings] with its notification.
///
/// If you set an alarm for the same [dateTime] as an existing one,
/// If you set an alarm for the same dateTime as an existing one,
/// the new alarm will replace the existing one.
static Future<bool> set({required AlarmSettings alarmSettings}) async {
alarmSettingsValidation(alarmSettings);
Expand All @@ -82,7 +84,7 @@ class Alarm {
() => ringStream.add(alarmSettings),
);
} else if (android) {
return await AndroidAlarm.set(
return AndroidAlarm.set(
alarmSettings,
() => ringStream.add(alarmSettings),
);
Expand All @@ -91,6 +93,7 @@ class Alarm {
return false;
}

/// Validates [alarmSettings] fields.
static void alarmSettingsValidation(AlarmSettings alarmSettings) {
if (alarmSettings.id == 0 || alarmSettings.id == -1) {
throw AlarmException(
Expand All @@ -99,12 +102,12 @@ class Alarm {
}
if (alarmSettings.id > 2147483647) {
throw AlarmException(
'Alarm id cannot be set larger than Int max value (2147483647). Provided: ${alarmSettings.id}',
'''Alarm id cannot be set larger than Int max value (2147483647). Provided: ${alarmSettings.id}''',
);
}
if (alarmSettings.id < -2147483648) {
throw AlarmException(
'Alarm id cannot be set smaller than Int min value (-2147483648). Provided: ${alarmSettings.id}',
'''Alarm id cannot be set smaller than Int min value (-2147483648). Provided: ${alarmSettings.id}''',
);
}
if (alarmSettings.volume != null &&
Expand All @@ -115,7 +118,7 @@ class Alarm {
}
if (alarmSettings.fadeDuration < 0) {
throw AlarmException(
'Fade duration must be positive. Provided: ${alarmSettings.fadeDuration}',
'''Fade duration must be positive. Provided: ${alarmSettings.fadeDuration}''',
);
}
}
Expand All @@ -127,7 +130,8 @@ class Alarm {
///
/// [title] default value is `Your alarm may not ring`
///
/// [body] default value is `You killed the app. Please reopen so your alarm can ring.`
/// [body] default value is `You killed the app.
/// Please reopen so your alarm can ring.`
static Future<void> setNotificationOnAppKillContent(
String title,
String body,
Expand Down Expand Up @@ -160,7 +164,7 @@ class Alarm {

/// Returns alarm by given id. Returns null if not found.
static AlarmSettings? getAlarm(int id) {
List<AlarmSettings> alarms = AlarmStorage.getSavedAlarms();
final alarms = AlarmStorage.getSavedAlarms();

for (final alarm in alarms) {
if (alarm.id == id) return alarm;
Expand All @@ -173,22 +177,3 @@ class Alarm {
/// Returns all the alarms.
static List<AlarmSettings> getAlarms() => AlarmStorage.getSavedAlarms();
}

class AlarmException implements Exception {
final String message;

const AlarmException(this.message);

@override
String toString() => message;
}

extension DateTimeExtension on DateTime {
bool isSameSecond(DateTime other) =>
year == other.year &&
month == other.month &&
day == other.day &&
hour == other.hour &&
minute == other.minute &&
second == other.second;
}
117 changes: 68 additions & 49 deletions lib/model/alarm_settings.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
import 'package:flutter/widgets.dart';

/// [AlarmSettings] is a model that contains all the settings to customize
/// and set an alarm.
@immutable
class AlarmSettings {
/// Model that contains all the settings to customize and set an alarm.
///
///
/// Note that if you want to show a notification when alarm is triggered, both
/// [notificationTitle] and [notificationBody] must not be null nor empty.
const AlarmSettings({
required this.id,
required this.dateTime,
required this.assetAudioPath,
required this.notificationTitle,
required this.notificationBody,
this.loopAudio = true,
this.vibrate = true,
this.volume,
this.fadeDuration = 0.0,
this.enableNotificationOnKill = true,
this.androidFullScreenIntent = true,
});

/// Constructs an `AlarmSettings` instance from the given JSON data.
factory AlarmSettings.fromJson(Map<String, dynamic> json) => AlarmSettings(
id: json['id'] as int,
dateTime: DateTime.fromMicrosecondsSinceEpoch(json['dateTime'] as int),
assetAudioPath: json['assetAudioPath'] as String,
loopAudio: json['loopAudio'] 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? ?? '',
enableNotificationOnKill:
json['enableNotificationOnKill'] as bool? ?? true,
androidFullScreenIntent:
json['androidFullScreenIntent'] as bool? ?? true,
);

/// Unique identifier assiocated with the alarm. Cannot be 0 or -1;
final int id;

Expand All @@ -7,15 +48,24 @@ class AlarmSettings {

/// Path to audio asset to be used as the alarm ringtone. Accepted formats:
///
/// * **Project asset**: Specifies an asset bundled with your Flutter project. Use this format for assets that are included in your project's `pubspec.yaml` file.
/// * **Project asset**: Specifies an asset bundled with your Flutter project.
/// Use this format for assets that are included in your project's
/// `pubspec.yaml` file.
/// Example: `assets/audio.mp3`.
/// * **Absolute file path**: Specifies a direct file system path to the audio file. This format is used for audio files stored outside the Flutter project, such as files saved in the device's internal or external storage.
/// * **Absolute file path**: Specifies a direct file system path to the
/// audio file. This format is used for audio files stored outside the
/// Flutter project, such as files saved in the device's internal
/// or external storage.
/// Example: `/path/to/your/audio.mp3`.
/// * **Relative file path**: Specifies a file path relative to a predefined base directory in the app's internal storage. This format is convenient for referring to files that are stored within a specific directory of your app's internal storage without needing to specify the full path.
/// * **Relative file path**: Specifies a file path relative to a predefined
/// base directory in the app's internal storage. This format is convenient
/// for referring to files that are stored within a specific directory of
/// your app's internal storage without needing to specify the full path.
/// Example: `Audios/audio.mp3`.
///
/// If you want to use aboslute or relative file path, you must request android storage
/// permission and add the following permission to your `AndroidManifest.xml`:
/// If you want to use aboslute or relative file path, you must request
/// android storage permission and add the following permission to your
/// `AndroidManifest.xml`:
/// `android.permission.READ_EXTERNAL_STORAGE`
final String assetAudioPath;

Expand All @@ -30,11 +80,13 @@ class AlarmSettings {

/// 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
/// 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.
/// 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;

Expand All @@ -50,14 +102,16 @@ class AlarmSettings {

/// Whether to show a notification when application is killed to warn
/// the user that the alarms won't ring anymore. Enabled by default.
/// Recommanded for iOS.
///
/// Not necessary for Android. Recommanded for iOS.
final bool enableNotificationOnKill;

/// Whether to turn screen on and display full screen notification
/// when android alarm notification is triggered. Enabled by default.
final bool androidFullScreenIntent;

/// Returns a hash code for this `AlarmSettings` instance using Jenkins hash function.
/// Returns a hash code for this `AlarmSettings` instance using
/// Jenkins hash function.
@override
int get hashCode {
var hash = 0;
Expand All @@ -77,42 +131,6 @@ class AlarmSettings {
return hash;
}

/// Model that contains all the settings to customize and set an alarm.
///
///
/// Note that if you want to show a notification when alarm is triggered,
/// both [notificationTitle] and [notificationBody] must not be null nor empty.
const AlarmSettings({
required this.id,
required this.dateTime,
required this.assetAudioPath,
this.loopAudio = true,
this.vibrate = true,
this.volume,
this.fadeDuration = 0.0,
required this.notificationTitle,
required this.notificationBody,
this.enableNotificationOnKill = true,
this.androidFullScreenIntent = true,
});

/// Constructs an `AlarmSettings` instance from the given JSON data.
factory AlarmSettings.fromJson(Map<String, dynamic> json) => AlarmSettings(
id: json['id'] as int,
dateTime: DateTime.fromMicrosecondsSinceEpoch(json['dateTime'] as int),
assetAudioPath: json['assetAudioPath'] as String,
loopAudio: json['loopAudio'] 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? ?? '',
enableNotificationOnKill:
json['enableNotificationOnKill'] as bool? ?? true,
androidFullScreenIntent:
json['androidFullScreenIntent'] as bool? ?? true,
);

/// Creates a copy of `AlarmSettings` but with the given fields replaced with
/// the new values.
AlarmSettings copyWith({
Expand Down Expand Up @@ -163,10 +181,11 @@ class AlarmSettings {
/// Returns all the properties of `AlarmSettings` for debug purposes.
@override
String toString() {
Map<String, dynamic> json = toJson();
json['dateTime'] = DateTime.fromMicrosecondsSinceEpoch(json['dateTime']);
final json = toJson();
json['dateTime'] =
DateTime.fromMicrosecondsSinceEpoch(json['dateTime'] as int);

return "AlarmSettings: ${json.toString()}";
return 'AlarmSettings: $json';
}

/// Compares two AlarmSettings.
Expand Down
Loading

0 comments on commit 2744961

Please sign in to comment.