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

Migrate to Pigeon #292

Merged
merged 7 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 6 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
include: package:very_good_analysis/analysis_options.yaml
include: package:very_good_analysis/analysis_options.yaml

analyzer:
exclude: [lib/src/generated/**]
errors:
todo: ignore
254 changes: 11 additions & 243 deletions android/src/main/kotlin/com/gdelataillade/alarm/alarm/AlarmPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,254 +1,22 @@
package com.gdelataillade.alarm.alarm

import com.gdelataillade.alarm.services.NotificationOnKillService
import com.gdelataillade.alarm.services.AlarmStorage
import com.gdelataillade.alarm.models.AlarmSettings

import android.os.Build
import android.os.Handler
import android.os.Looper
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.annotation.NonNull
import java.util.Date
import AlarmApi
import AlarmTriggerApi
import com.gdelataillade.alarm.api.AlarmApiImpl
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.Log
import com.google.gson.Gson

class AlarmPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var context: Context
private lateinit var methodChannel: MethodChannel
private lateinit var eventChannel: EventChannel

private val alarmIds: MutableList<Int> = mutableListOf()
private var notifOnKillEnabled: Boolean = false
private var notificationOnKillTitle: String = "Your alarms may not ring"
private var notificationOnKillBody: String = "You killed the app. Please reopen so your alarms can be rescheduled."

class AlarmPlugin : FlutterPlugin {
companion object {
@JvmStatic
var eventSink: EventChannel.EventSink? = null
}

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext

methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.gdelataillade.alarm/alarm")
methodChannel.setMethodCallHandler(this)

eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.gdelataillade.alarm/events")
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}

override fun onCancel(arguments: Any?) {
eventSink = null
}
})
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
when (call.method) {
"setAlarm" -> {
setAlarm(call, result)
}
"stopAlarm" -> {
val id = call.argument<Int>("id")
if (id == null) {
result.error("INVALID_ID", "Alarm ID is null", null)
return
}

stopAlarm(id, result)
}
"isRinging" -> {
val id = call.argument<Int>("id")
val ringingAlarmIds = AlarmService.ringingAlarmIds
val isRinging = if (id == null) {
ringingAlarmIds.isNotEmpty()
} else {
ringingAlarmIds.contains(id)
}
result.success(isRinging)
}
"setWarningNotificationOnKill" -> {
val title = call.argument<String>("title")
val body = call.argument<String>("body")
if (title != null && body != null) {
notificationOnKillTitle = title
notificationOnKillBody = body
}
result.success(true)
}
"disableWarningNotificationOnKill" -> {
disableWarningNotificationOnKill(context)
result.success(true)
}
else -> {
result.notImplemented()
}
}
}

fun setAlarm(call: MethodCall, result: MethodChannel.Result, customContext: Context? = null) {
val alarmJsonMap = call.arguments as? Map<String, Any>
val contextToUse = customContext ?: context

if (alarmJsonMap != null) {
try {
val gson = Gson()
val alarmJsonString = gson.toJson(alarmJsonMap)
val alarm = AlarmSettings.fromJson(alarmJsonString)

if (alarm != null) {
val alarmIntent = createAlarmIntent(contextToUse, call, alarm.id)
val delayInSeconds = (alarm.dateTime.time - System.currentTimeMillis()) / 1000

if (delayInSeconds <= 5) {
handleImmediateAlarm(contextToUse, alarmIntent, delayInSeconds.toInt())
} else {
handleDelayedAlarm(
contextToUse,
alarmIntent,
delayInSeconds.toInt(),
alarm.id,
alarm.warningNotificationOnKill
)
}
alarmIds.add(alarm.id)
result.success(true)
} else {
result.error("INVALID_ALARM", "Failed to parse alarm JSON", null)
}
} catch (e: Exception) {
Log.e("AlarmPlugin", "Error parsing alarmJsonMap: ${e.message}", e)
result.error("PARSE_ERROR", "Error parsing alarmJsonMap", e.message)
}
} else {
result.error("INVALID_ARGUMENTS", "Invalid arguments provided for setAlarm", null)
}
}

fun stopAlarm(id: Int, result: MethodChannel.Result? = null) {
if (AlarmService.ringingAlarmIds.contains(id)) {
val stopIntent = Intent(context, AlarmService::class.java)
stopIntent.action = "STOP_ALARM"
stopIntent.putExtra("id", id)
context.stopService(stopIntent)
}

// Intent to cancel the future alarm if it's set
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
id,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

// Cancel the future alarm using AlarmManager
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pendingIntent)

alarmIds.remove(id)
if (alarmIds.isEmpty() && notifOnKillEnabled) {
disableWarningNotificationOnKill(context)
}

result?.success(true)
}

fun createAlarmIntent(context: Context, call: MethodCall, id: Int?): Intent {
val alarmIntent = Intent(context, AlarmReceiver::class.java)
setIntentExtras(alarmIntent, call, id)
return alarmIntent
}

fun setIntentExtras(intent: Intent, call: MethodCall, id: Int?) {
intent.putExtra("id", id)
intent.putExtra("assetAudioPath", call.argument<String>("assetAudioPath"))
intent.putExtra("loopAudio", call.argument<Boolean>("loopAudio") ?: true)
intent.putExtra("vibrate", call.argument<Boolean>("vibrate") ?: true)
intent.putExtra("volume", call.argument<Double>("volume"))
intent.putExtra("volumeEnforced", call.argument<Boolean>("volumeEnforced") ?: false)
intent.putExtra("fadeDuration", call.argument<Double>("fadeDuration") ?: 0.0)
intent.putExtra("fullScreenIntent", call.argument<Boolean>("androidFullScreenIntent") ?: true)

val notificationSettingsMap = call.argument<Map<String, Any>>("notificationSettings")
val gson = Gson()
val notificationSettingsJson = gson.toJson(notificationSettingsMap ?: emptyMap<String, Any>())
intent.putExtra("notificationSettings", notificationSettingsJson)
}

fun handleImmediateAlarm(context: Context, intent: Intent, delayInSeconds: Int) {
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
context.sendBroadcast(intent)
}, delayInSeconds * 1000L)
}

fun handleDelayedAlarm(
context: Context,
intent: Intent,
delayInSeconds: Int,
id: Int,
warningNotificationOnKill: Boolean
) {
try {
val triggerTime = System.currentTimeMillis() + delayInSeconds * 1000L
val pendingIntent = PendingIntent.getBroadcast(
context,
id,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
?: throw IllegalStateException("AlarmManager not available")

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
}

if (warningNotificationOnKill && !notifOnKillEnabled) {
setWarningNotificationOnKill(context)
}
} catch (e: ClassCastException) {
Log.e("AlarmPlugin", "AlarmManager service type casting failed", e)
} catch (e: IllegalStateException) {
Log.e("AlarmPlugin", "AlarmManager service not available", e)
} catch (e: Exception) {
Log.e("AlarmPlugin", "Error in handling delayed alarm", e)
}
}

fun setWarningNotificationOnKill(context: Context) {
val serviceIntent = Intent(context, NotificationOnKillService::class.java)
serviceIntent.putExtra("title", notificationOnKillTitle)
serviceIntent.putExtra("body", notificationOnKillBody)

context.startService(serviceIntent)
notifOnKillEnabled = true
var alarmTriggerApi: AlarmTriggerApi? = null
}

fun disableWarningNotificationOnKill(context: Context) {
val serviceIntent = Intent(context, NotificationOnKillService::class.java)
context.stopService(serviceIntent)
notifOnKillEnabled = false
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
AlarmApi.setUp(binding.binaryMessenger, AlarmApiImpl(binding.applicationContext))
alarmTriggerApi = AlarmTriggerApi(binding.binaryMessenger)
}

override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
alarmTriggerApi = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import io.flutter.Log

class AlarmReceiver : BroadcastReceiver() {
companion object {
Expand All @@ -24,7 +23,12 @@ class AlarmReceiver : BroadcastReceiver() {
serviceIntent.putExtras(intent)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val pendingIntent = PendingIntent.getForegroundService(context, 1, serviceIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent = PendingIntent.getForegroundService(
context,
1,
serviceIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
pendingIntent.send()
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
Expand Down
Loading