Skip to content

Commit

Permalink
fix(notification_android): Handle channels on android part to prevent…
Browse files Browse the repository at this point in the history
… notification when user is declared
  • Loading branch information
NyraSama committed Jun 26, 2024
1 parent 8e0e940 commit 8dfd998
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -1,40 +1,93 @@
package com.xpeho.yaki

import io.flutter.plugin.common.MethodChannel
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.util.Log
import androidx.core.app.NotificationCompat
import java.util.Calendar
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor

const val ALARM_ID = "Alarm"
const val CHANNEL_NAME = "com.xpeho.yaki/notification"

/** The receiver used to show a push notification when the alarm is triggered. */
class AlarmReceiver : BroadcastReceiver() {
/**
* This method is called when the alarm is triggered. We use it to show a push notification.
*
* @param context The context of the application
* @param intent The intent used to trigger the alarm
*/
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("AlarmReceiver", "Alarm received")

// Make sure that the notification is not shown on weekends
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
if (currentDay == Calendar.SATURDAY || currentDay == Calendar.SUNDAY) {
return
}

Log.d("AlarmReceiver", "Setup Flutter engine")
// Initialize the Flutter engine.
val flutterEngine = context?.let { FlutterEngine(it) }

// Start executing Dart code to initialize the Flutter engine.
flutterEngine?.dartExecutor?.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)

Log.d("AlarmReceiver", "Invoke Flutter method")
// Use the MethodChannel to invoke a method on the Flutter side.
flutterEngine?.dartExecutor?.let { MethodChannel(it.binaryMessenger, CHANNEL_NAME) }
?.invokeMethod("isDeclaredToday", null, object : MethodChannel.Result {
override fun success(result: Any?) {
// Handle success.
Log.d("AlarmReceiver", "Invoke success")
Log.d("AlarmReceiver", result.toString())
if (result as? Boolean == true) {

return
} else {
showNotification(context, intent)
}
}

override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
// Handle error.
Log.d("AlarmReceiver", "Invoke error")
Log.d("AlarmReceiver", errorMessage.toString())
}

override fun notImplemented() {
// Handle method not implemented in Flutter.
Log.d("AlarmReceiver", "Invoke notImplemented")
}
})
}

/**
* This method is called when the alarm is triggered. We use it to show a push notification.
* Show a push notification.
*
* @param context The context of the application
* @param intent The intent used to trigger the alarm
* @param title The title of the notification
* @param message The message of the notification
*/
override fun onReceive(context: Context?, intent: Intent?) {
private fun showNotification(context: Context?, intent: Intent?){
// Get the message from the intent
if (intent == null) {
return
}
val notificationTitle = intent.getStringExtra("title") ?: return
val notificationMessage = intent.getStringExtra("message") ?: return

// Make sure that the notification is not shown on weekends
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
if (currentDay == Calendar.SATURDAY || currentDay == Calendar.SUNDAY) {
return
}

// Check if the context is not null
if (context != null) {
// Create the notification channel
val notificationManager =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ class AlarmSchedulerImpl(private val context: Context) : AlarmScheduler {
}

// The time of the alarm defined in the local time zone
val zonedDateTime = item.time.atZone(ZoneId.systemDefault())
//val zonedDateTime = item.time.atZone(ZoneId.systemDefault())
// The calendar used to schedule the alarm
val calendar =
Calendar.getInstance().apply { timeInMillis = zonedDateTime.toInstant().toEpochMilli() }
Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis() //zonedDateTime.toInstant().toEpochMilli()
}
// If the alarm time is in the past, we schedule it for the next day
if (calendar.timeInMillis < System.currentTimeMillis()) {
/* if (calendar.timeInMillis < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
}
} */

// The intent used to schedule the alarm
val intent =
Expand All @@ -54,7 +56,7 @@ class AlarmSchedulerImpl(private val context: Context) : AlarmScheduler {
alarmManager.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
60000, //AlarmManager.INTERVAL_DAY,
pendingIntent
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,30 @@ class MainActivity : FlutterActivity() {
private lateinit var alarmScheduler: AlarmSchedulerImpl

/**
* This method is called when the app is launched. We use it to initialize the alarm scheduler and
* to initialize the params of the notification.
* This method define a channel connexion to Flutter. We use it to give the Flutter part the
* ability to manage the notifications.
*/
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
alarmScheduler = AlarmSchedulerImpl(context = this)
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

methodChannel().setMethodCallHandler {
call,
result ->
when (call.method) {
"scheduleNotifications" -> {
scheduleNotifications()
result.success(null)
}
"cancelNotifications" -> {
cancelNotifications()
result.success(null)
}
"areNotificationsPermitted" -> {
result.success(areNotificationsPermitted())
}
else -> result.notImplemented()
}
}
}

private fun methodChannel(): MethodChannel {
Expand All @@ -40,6 +58,15 @@ class MainActivity : FlutterActivity() {
return result
}

/**
* This method is called when the app is launched. We use it to initialize the alarm scheduler and
* to initialize the params of the notification.
*/
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
alarmScheduler = AlarmSchedulerImpl(context = this)
}

/**
* This method is called when the app is resumed. We use it to schedule the alarm if the following
* conditions are met:
Expand All @@ -49,8 +76,8 @@ class MainActivity : FlutterActivity() {
override fun onResume() {
super.onResume()

isDeclaredToday {
Log.d("MainActivity", "isDeclaredToday: $it")
isDeclaredToday { isDeclared ->
Log.d("MainActivity", "isDeclaredToday: $isDeclared")
// Note(Loucas): The notification disabling logic should happen when calling isDeclaredToday.
// Please note that this code is based on completion closures, but can be converted to async await syntax.
// https://betterprogramming.pub/kotlin-suspending-functions-as-swift-async-await-with-adapter-pattern-a79aacaa5b1c
Expand All @@ -76,9 +103,12 @@ class MainActivity : FlutterActivity() {
}
}
override fun error(code: String, message: String?, details: Any?) {
Log.e("MainActivity", "areNotificationsActivated error")
message?.let { Log.e("MainActivity", it) }
return
}
override fun notImplemented() {
Log.e("MainActivity", "areNotificationsActivated not implemented")
return
}
}
Expand All @@ -89,33 +119,6 @@ class MainActivity : FlutterActivity() {
}
}

/**
* This method define a channel connexion to Flutter. We use it to give the Flutter part the
* ability to manage the notifications.
*/
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

methodChannel().setMethodCallHandler {
call,
result ->
when (call.method) {
"scheduleNotifications" -> {
scheduleNotifications()
result.success(null)
}
"cancelNotifications" -> {
cancelNotifications()
result.success(null)
}
"areNotificationsPermitted" -> {
result.success(areNotificationsPermitted())
}
else -> result.notImplemented()
}
}
}

/**
* This method is a channel method that schedule the notifications if the permissions are granted
* and ask the permissions if not. It also check before if the notifications are already scheduled
Expand Down Expand Up @@ -173,7 +176,7 @@ class MainActivity : FlutterActivity() {
context.startActivity(intent)
}

private fun isDeclaredToday(resultHandler: (Boolean) -> Unit) {
fun isDeclaredToday(resultHandler: (Boolean) -> Unit) {
methodChannel().invokeMethod("isDeclaredToday", null, object : MethodChannel.Result {
override fun success(result: Any?) {
resultHandler(result as? Boolean ?: false)
Expand Down
7 changes: 3 additions & 4 deletions yaki_mobile/lib/channels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,10 @@ Future<bool> isDeclaredToday() async {
);
final declaredDays = await declarationRepository.getDeclaredDays(userId);
final today = DateFormat("yyyy-MM-dd").format(DateTime.now());
final closestDay = declaredDays.reduce((a, b) => a.compareTo(b) <= 0 ? a : b);
debugPrint("isDeclaredToday, closestDay : $closestDay");
debugPrint("isDeclaredToday, function result : ${closestDay == today}");
final result = declaredDays.contains(today);
debugPrint("isDeclaredToday, function result : $result");

return Future.value(closestDay == today);
return Future.value(result);
}

//------------------------FLUTTER CALL TO NATIVE METHOD---------------------------
Expand Down
Loading

0 comments on commit 8dfd998

Please sign in to comment.