Skip to content

Commit

Permalink
Remove snooze action button
Browse files Browse the repository at this point in the history
  • Loading branch information
gdelataillade committed Aug 7, 2024
1 parent 22e4f71 commit 551b1d7
Show file tree
Hide file tree
Showing 18 changed files with 141 additions and 232 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ notificationTitle | `String` | The title of the notification triggered whe
notificationBody | `String` | The body of the notification.
enableNotificationOnKill | `bool` | Whether to show a notification when application is killed to warn the user that the alarm he set may not ring. Enabled by default.
androidFullScreenIntent | `bool` | Whether to turn screen on when android alarm notification is triggered. Enabled by default.
notificationActionSettings | `NotificationActionSettings` | Settings for notification action buttons (only stop at the moment).

Note that if `notificationTitle` and `notificationBody` are both empty, iOS will not show the notification and Android will show an empty notification.

Expand Down Expand Up @@ -181,7 +182,6 @@ We welcome contributions to this plugin! If you would like to make a change or a

These are some features that I have in mind that could be useful:
- [Android] Reschedule alarms after device reboot.
- Notification actions: stop and snooze.
- Use `ffigen` and `jnigen` binding generators to call native code more efficiently instead of using method channels.
- Stop alarm sound when notification is dismissed.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.annotation.NonNull
import java.util.Date
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
Expand Down Expand Up @@ -64,19 +65,19 @@ class AlarmPlugin: FlutterPlugin, MethodCallHandler {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("AlarmPlugin", "Received alarm notification action")
val action = intent?.getStringExtra(AlarmReceiver.EXTRA_ALARM_ACTION)
val alarmId = intent?.getIntExtra("id", -1) ?: -1
val id = intent?.getIntExtra("id", -1) ?: -1

when (action) {
NotificationHandler.ACTION_STOP -> {
Log.d("AlarmPlugin", "Notification action: STOP, id: $alarmId")
stopAlarm(alarmId)
}
NotificationHandler.ACTION_SNOOZE -> {
Log.d("AlarmPlugin", "Notification action: SNOOZE, id: $alarmId")
if (context == null || alarmId == -1) {
Log.e("AlarmPlugin", "Snooze alarm error")
return
Log.d("AlarmPlugin", "Notification action: STOP, id: $id")
stopAlarm(id)
if (context != null) {
AlarmStorage(context!!).unsaveAlarm(id)
eventSink?.success(mapOf(
"id" to id,
"method" to "stop"
))
}
snoozeAlarm(context, alarmId, 9 * 60)
}
}
}
Expand Down Expand Up @@ -254,12 +255,6 @@ class AlarmPlugin: FlutterPlugin, MethodCallHandler {
notifOnKillEnabled = false
}

fun snoozeAlarm(context: Context, id: Int, snoozeInSeconds: Int) {
Log.d("AlarmPlugin", "Snoozing alarm with id: $id")

// get alarm intent with id, stop it and reschedule with extras and snooze time
}

override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,16 @@ class AlarmService : Service() {
return START_NOT_STICKY
}

// Only Android 12 and above ?
val action = intent.action
Log.d("AlarmService", "Received action: $action")
Log.d("AlarmService", "-> Received action: $action")
val id = intent.getIntExtra("id", 0)
if (action == "STOP_ALARM" && id != 0) {
stopAlarm(id)
return START_NOT_STICKY
} else if (action == NotificationHandler.ACTION_STOP) {
Log.d("AlarmService", "Action button clicked: STOP")
return START_NOT_STICKY
} else if (action == NotificationHandler.ACTION_SNOOZE) {
Log.d("AlarmService", "Action button clicked: SNOOZE")
stopAlarm(id)
return START_NOT_STICKY
}

Expand Down Expand Up @@ -97,7 +96,10 @@ class AlarmService : Service() {
return START_NOT_STICKY // Return on security exception
}

AlarmPlugin.eventSink?.success(mapOf("id" to id))
AlarmPlugin.eventSink?.success(mapOf(
"id" to id,
"method" to "ring"
))

if (volume >= 0.0 && volume <= 1.0) {
volumeService?.setVolume(volume, showSystemUI)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import com.google.gson.Gson

data class NotificationActionSettings(
val hasStopButton: Boolean = false,
val hasSnoozeButton: Boolean = false,
val stopButtonText: String = "Stop",
val snoozeButtonText: String = "Snooze",
val snoozeDurationInSeconds: Int = 9 * 60
val stopButtonText: String = "Stop"
) {
companion object {
fun fromJson(json: Map<String, Any>): NotificationActionSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ class AlarmStorage(context: Context) {

fun unsaveAlarm(id: Int) {
val key = "$PREFIX$id"
Log.d("AlarmStorage", "Unsaving alarm with id: $id and key: $key")
val editor = prefs.edit()
editor.remove(key)
Log.d("AlarmStorage", "Removed alarm with key: $key from preferences")
editor.apply()
Log.d("AlarmStorage", "Changes applied to preferences")
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class NotificationHandler(private val context: Context) {
private const val CHANNEL_ID = "alarm_plugin_channel"
private const val CHANNEL_NAME = "Alarm Notification"
const val ACTION_STOP = "com.gdelataillade.alarm.ACTION_STOP"
const val ACTION_SNOOZE = "com.gdelataillade.alarm.ACTION_SNOOZE"
}

init {
Expand All @@ -37,6 +36,7 @@ class NotificationHandler(private val context: Context) {
}
}

// TODO: Add wake lock with loop param
fun buildNotification(
title: String,
body: String,
Expand All @@ -58,17 +58,6 @@ class NotificationHandler(private val context: Context) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val snoozeIntent = Intent(context, AlarmReceiver::class.java).apply {
action = ACTION_SNOOZE
putExtra("id", alarmId)
}
val snoozePendingIntent = PendingIntent.getBroadcast(
context,
1,
snoozeIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(appIconResId)
.setContentTitle(title)
Expand All @@ -89,9 +78,6 @@ class NotificationHandler(private val context: Context) {
if (it.hasStopButton) {
notificationBuilder.addAction(0, it.stopButtonText, stopPendingIntent)
}
if (it.hasSnoozeButton) {
notificationBuilder.addAction(0, it.snoozeButtonText, snoozePendingIntent)
}
}

return notificationBuilder.build()
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarmExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
Expand Down Expand Up @@ -522,7 +522,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarmExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
Expand Down Expand Up @@ -550,7 +550,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarm.alarmExample;
PRODUCT_BUNDLE_IDENTIFIER = com.gdelataillade.alarmExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
Expand Down
3 changes: 0 additions & 3 deletions example/lib/screens/edit_alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,6 @@ class _ExampleAlarmEditScreenState extends State<ExampleAlarmEditScreen> {
enableNotificationOnKill: Platform.isIOS,
notificationActionSettings: const NotificationActionSettings(
hasStopButton: true,
stopButtonText: 'STOP !',
snoozeButtonText: 'SNOOZE !',
snoozeDurationInSeconds: 5,
),
);
return alarmSettings;
Expand Down
51 changes: 11 additions & 40 deletions example/lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import 'package:alarm/alarm.dart';
import 'package:alarm_example/screens/edit_alarm.dart';
import 'package:alarm_example/screens/ring.dart';
import 'package:alarm_example/screens/shortcut_button.dart';
import 'package:alarm_example/services/permission.dart';
import 'package:alarm_example/widgets/tile.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

class ExampleAlarmHomeScreen extends StatefulWidget {
const ExampleAlarmHomeScreen({super.key});
Expand All @@ -18,17 +18,21 @@ class ExampleAlarmHomeScreen extends StatefulWidget {
class _ExampleAlarmHomeScreenState extends State<ExampleAlarmHomeScreen> {
late List<AlarmSettings> alarms;

static StreamSubscription<AlarmSettings>? subscription;
static StreamSubscription<AlarmSettings>? ringSubscription;
static StreamSubscription<int>? updateSubscription;

@override
void initState() {
super.initState();
if (Alarm.android) {
checkAndroidNotificationPermission();
checkAndroidScheduleExactAlarmPermission();
AlarmPermissions.checkAndroidNotificationPermission();
AlarmPermissions.checkAndroidScheduleExactAlarmPermission();
}
loadAlarms();
subscription ??= Alarm.ringStream.stream.listen(navigateToRingScreen);
ringSubscription ??= Alarm.ringStream.stream.listen(navigateToRingScreen);
updateSubscription ??= Alarm.updateStream.stream.listen((_) {
loadAlarms();
});
}

void loadAlarms() {
Expand Down Expand Up @@ -67,43 +71,10 @@ class _ExampleAlarmHomeScreenState extends State<ExampleAlarmHomeScreen> {
if (res != null && res == true) loadAlarms();
}

Future<void> checkAndroidNotificationPermission() async {
final status = await Permission.notification.status;
if (status.isDenied) {
alarmPrint('Requesting notification permission...');
final res = await Permission.notification.request();
alarmPrint(
'Notification permission ${res.isGranted ? '' : 'not '}granted',
);
}
}

Future<void> checkAndroidExternalStoragePermission() async {
final status = await Permission.storage.status;
if (status.isDenied) {
alarmPrint('Requesting external storage permission...');
final res = await Permission.storage.request();
alarmPrint(
'External storage permission ${res.isGranted ? '' : 'not'} granted',
);
}
}

Future<void> checkAndroidScheduleExactAlarmPermission() async {
final status = await Permission.scheduleExactAlarm.status;
alarmPrint('Schedule exact alarm permission: $status.');
if (status.isDenied) {
alarmPrint('Requesting schedule exact alarm permission...');
final res = await Permission.scheduleExactAlarm.request();
alarmPrint(
'Schedule exact alarm permission ${res.isGranted ? '' : 'not'} granted',
);
}
}

@override
void dispose() {
subscription?.cancel();
ringSubscription?.cancel();
updateSubscription?.cancel();
super.dispose();
}

Expand Down
4 changes: 0 additions & 4 deletions example/lib/screens/shortcut_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ class _ExampleAlarmHomeShortcutButtonState
enableNotificationOnKill: Platform.isIOS,
notificationActionSettings: const NotificationActionSettings(
hasStopButton: true,
hasSnoozeButton: true,
stopButtonText: 'STOP',
snoozeButtonText: 'SNOOZE',
snoozeDurationInSeconds: 10,
),
);

Expand Down
38 changes: 38 additions & 0 deletions example/lib/services/permission.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:alarm/alarm.dart';
import 'package:permission_handler/permission_handler.dart';

class AlarmPermissions {
static Future<void> checkAndroidNotificationPermission() async {
final status = await Permission.notification.status;
if (status.isDenied) {
alarmPrint('Requesting notification permission...');
final res = await Permission.notification.request();
alarmPrint(
'Notification permission ${res.isGranted ? '' : 'not '}granted',
);
}
}

static Future<void> checkAndroidExternalStoragePermission() async {
final status = await Permission.storage.status;
if (status.isDenied) {
alarmPrint('Requesting external storage permission...');
final res = await Permission.storage.request();
alarmPrint(
'External storage permission ${res.isGranted ? '' : 'not'} granted',
);
}
}

static Future<void> checkAndroidScheduleExactAlarmPermission() async {
final status = await Permission.scheduleExactAlarm.status;
alarmPrint('Schedule exact alarm permission: $status.');
if (status.isDenied) {
alarmPrint('Requesting schedule exact alarm permission...');
final res = await Permission.scheduleExactAlarm.request();
alarmPrint(
'Schedule exact alarm permission ${res.isGranted ? '' : 'not'} granted',
);
}
}
}
9 changes: 1 addition & 8 deletions ios/Classes/SwiftAlarmPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,7 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
self.stopAlarm(id: id, cancelNotif: true, result: { _ in })
}
NSLog("SwiftAlarmPlugin: stopAlarmFromNotification...")
// channel.invokeMethod("alarmStoppedFromNotification", arguments: ["id": id])
}

func snoozeAlarmFromNotification(id: Int, snoozeDurationInSeconds: Int) {
safeModifyResources {
self.stopAlarm(id: id, cancelNotif: true, result: { _ in })
}
// channel.invokeMethod("alarmSnoozedFromNotification", arguments: ["id": id, "snoozeDurationInSeconds": snoozeDurationInSeconds])
channel.invokeMethod("alarmStoppedFromNotification", arguments: ["id": id])
}

func safeModifyResources(_ modificationBlock: @escaping () -> Void) {
Expand Down
13 changes: 2 additions & 11 deletions ios/Classes/models/NotificationActionSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,19 @@ import Foundation

struct NotificationActionSettings: Codable {
var hasStopButton: Bool = false
var hasSnoozeButton: Bool = false
var stopButtonText: String = "Stop"
var snoozeButtonText: String = "Snooze"
var snoozeDurationInSeconds: Int = 9 * 60

static func fromJson(json: [String: Any]) -> NotificationActionSettings {
return NotificationActionSettings(
hasStopButton: json["hasStopButton"] as? Bool ?? false,
hasSnoozeButton: json["hasSnoozeButton"] as? Bool ?? false,
stopButtonText: json["stopButtonText"] as? String ?? "Stop",
snoozeButtonText: json["snoozeButtonText"] as? String ?? "Snooze",
snoozeDurationInSeconds: json["snoozeDurationInSeconds"] as? Int ?? 9 * 60
stopButtonText: json["stopButtonText"] as? String ?? "Stop"
)
}

static func toJson(notificationActionSettings: NotificationActionSettings) -> [String: Any] {
return [
"hasStopButton": notificationActionSettings.hasStopButton,
"hasSnoozeButton": notificationActionSettings.hasSnoozeButton,
"stopButtonText": notificationActionSettings.stopButtonText,
"snoozeButtonText": notificationActionSettings.snoozeButtonText,
"snoozeDurationInSeconds": notificationActionSettings.snoozeDurationInSeconds
"stopButtonText": notificationActionSettings.stopButtonText
]
}
}
Loading

0 comments on commit 551b1d7

Please sign in to comment.