Get basic timer notifications working

This commit is contained in:
2023-04-04 18:20:47 +12:00
parent 7d4b6a45c5
commit 117ca5cdc4
9 changed files with 418 additions and 24 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.fmassive">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -0,0 +1,191 @@
package com.example.fmassive
import android.annotation.SuppressLint
import android.app.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.CountDownTimer
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import kotlin.math.floor
class AlarmModule(private val context: Context) : MethodCallHandler {
private var countdownTimer: CountDownTimer? = null
var currentMs: Long = 0
private var running = false
private val stopReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("AlarmModule", "Received stop broadcast intent")
if (context != null) {
stop(context)
}
}
}
private val addReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null) {
add(context)
}
}
}
init {
}
@RequiresApi(api = Build.VERSION_CODES.O)
fun add(context: Context) {
Log.d("AlarmModule", "Add 1 min to alarm.")
countdownTimer?.cancel()
val newMs = if (running) currentMs.toInt().plus(60000) else 60000
countdownTimer = getTimer(context, newMs)
countdownTimer?.start()
running = true
//val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//val intent = Intent(context, AlarmService::class.java)
//context.stopService(intent)
}
@RequiresApi(api = Build.VERSION_CODES.O)
fun stop(context: Context) {
Log.d("AlarmModule", "Stop alarm.")
countdownTimer?.cancel()
running = false
//val intent = Intent(context, AlarmService::class.java)
//reactApplicationContext?.stopService(intent)
//val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//manager.cancel(NOTIFICATION_ID_PENDING)
//val params = Arguments.createMap().apply {
// putString("minutes", "00")
// putString("seconds", "00")
//}
//reactApplicationContext
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("tick", params)
}
@RequiresApi(api = Build.VERSION_CODES.O)
fun timer(context: Context, milliseconds: Int) {
context.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
context.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
val manager = getManager(context)
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//val intent = Intent(reactApplicationContext, AlarmService::class.java)
//reactApplicationContext.stopService(intent)
countdownTimer?.cancel()
countdownTimer = getTimer(context, milliseconds)
countdownTimer?.start()
running = true
}
@RequiresApi(Build.VERSION_CODES.M)
private fun getTimer(
context: Context,
endMs: Int,
): CountDownTimer {
val builder = getBuilder(context)
return object : CountDownTimer(endMs.toLong(), 1000) {
@RequiresApi(Build.VERSION_CODES.O)
override fun onTick(current: Long) {
currentMs = current
val seconds =
floor((current / 1000).toDouble() % 60).toInt().toString().padStart(2, '0')
val minutes =
floor((current / 1000).toDouble() / 60).toInt().toString().padStart(2, '0')
builder.setContentText("$minutes:$seconds").setAutoCancel(false).setDefaults(0)
.setProgress(endMs, current.toInt(), false)
.setCategory(NotificationCompat.CATEGORY_PROGRESS).priority =
NotificationCompat.PRIORITY_LOW
val manager = getManager(context)
manager.notify(NOTIFICATION_ID_PENDING, builder.build())
//val params = Arguments.createMap().apply {
// putString("minutes", minutes)
// putString("seconds", seconds)
//}
//reactApplicationContext
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("tick", params)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onFinish() {
//val context = reactApplicationContext
//context.startForegroundService(Intent(context, AlarmService::class.java))
//context
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("finish", Arguments.createMap().apply {
// putString("minutes", "00")
// putString("seconds", "00")
// })
}
}
}
@SuppressLint("UnspecifiedImmutableFlag")
@RequiresApi(Build.VERSION_CODES.M)
private fun getBuilder(context: Context): NotificationCompat.Builder {
val contentIntent = Intent(context, MainActivity::class.java)
val pendingContent =
PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
val addBroadcast = Intent(ADD_BROADCAST).apply {
setPackage(context.packageName)
}
val pendingAdd =
PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
val stopBroadcast = Intent(STOP_BROADCAST)
stopBroadcast.setPackage(context.packageName)
val pendingStop =
PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
return NotificationCompat.Builder(context, CHANNEL_ID_PENDING)
.setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting")
.setContentIntent(pendingContent)
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
.setDeleteIntent(pendingStop)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun getManager(context: Context): NotificationManager {
val notificationManager = context.getSystemService(
NotificationManager::class.java
)
val timersChannel = NotificationChannel(
CHANNEL_ID_PENDING, CHANNEL_ID_PENDING, NotificationManager.IMPORTANCE_LOW
)
timersChannel.setSound(null, null)
timersChannel.description = "Progress on rest timers."
notificationManager.createNotificationChannel(timersChannel)
return notificationManager
}
companion object {
const val STOP_BROADCAST = "stop-timer-event"
const val ADD_BROADCAST = "add-timer-event"
const val CHANNEL_ID_PENDING = "Timer"
const val NOTIFICATION_ID_PENDING = 1
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"timer" -> {
}
else -> result.notImplemented()
}
}
}
@@ -1,6 +1,195 @@
package com.example.fmassive
import android.annotation.SuppressLint
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.content.IntentFilter
import android.os.Build
import android.os.CountDownTimer
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlin.math.floor
class MainActivity: FlutterActivity() {
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.massive/android"
@RequiresApi(Build.VERSION_CODES.O)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// This method is invoked on the main thread.
call, result ->
if (call.method == "timer") {
val args = call.arguments as ArrayList<Long>
timer(args[0])
} else {
result.notImplemented()
}
}
}
private var countdownTimer: CountDownTimer? = null
var currentMs: Long = 0
private var running = false
private val stopReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("AlarmModule", "Received stop broadcast intent")
stop()
}
}
private val addReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
add()
}
}
init {
}
@RequiresApi(api = Build.VERSION_CODES.O)
fun add() {
Log.d("AlarmModule", "Add 1 min to alarm.")
countdownTimer?.cancel()
val newMs = if (running) currentMs.toInt().plus(60000) else 60000
countdownTimer = getTimer(newMs.toLong())
countdownTimer?.start()
running = true
//val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//val intent = Intent(context, AlarmService::class.java)
//context.stopService(intent)
}
@RequiresApi(api = Build.VERSION_CODES.O)
fun stop() {
Log.d("AlarmModule", "Stop alarm.")
countdownTimer?.cancel()
running = false
//val intent = Intent(context, AlarmService::class.java)
//reactApplicationContext?.stopService(intent)
//val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//manager.cancel(NOTIFICATION_ID_PENDING)
//val params = Arguments.createMap().apply {
// putString("minutes", "00")
// putString("seconds", "00")
//}
//reactApplicationContext
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("tick", params)
}
@RequiresApi(api = Build.VERSION_CODES.O)
fun timer(milliseconds: Long) {
context.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
context.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
//val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//val intent = Intent(reactApplicationContext, AlarmService::class.java)
//reactApplicationContext.stopService(intent)
countdownTimer?.cancel()
countdownTimer = getTimer(milliseconds)
countdownTimer?.start()
running = true
}
@RequiresApi(Build.VERSION_CODES.M)
private fun getTimer(
endMs: Long,
): CountDownTimer {
val builder = getBuilder()
return object : CountDownTimer(endMs.toLong(), 1000) {
@RequiresApi(Build.VERSION_CODES.O)
override fun onTick(current: Long) {
currentMs = current
val seconds =
floor((current / 1000).toDouble() % 60).toInt().toString().padStart(2, '0')
val minutes =
floor((current / 1000).toDouble() / 60).toInt().toString().padStart(2, '0')
builder.setContentText("$minutes:$seconds").setAutoCancel(false).setDefaults(0)
.setProgress(endMs.toInt(), current.toInt(), false)
.setCategory(NotificationCompat.CATEGORY_PROGRESS).priority =
NotificationCompat.PRIORITY_LOW
val manager = getManager()
Log.d("AlarmModule", "Notify $NOTIFICATION_ID_PENDING")
manager.notify(NOTIFICATION_ID_PENDING, builder.build())
//val params = Arguments.createMap().apply {
// putString("minutes", minutes)
// putString("seconds", seconds)
//}
//reactApplicationContext
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("tick", params)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onFinish() {
//val context = reactApplicationContext
//context.startForegroundService(Intent(context, AlarmService::class.java))
//context
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("finish", Arguments.createMap().apply {
// putString("minutes", "00")
// putString("seconds", "00")
// })
}
}
}
@SuppressLint("UnspecifiedImmutableFlag")
@RequiresApi(Build.VERSION_CODES.M)
private fun getBuilder(): NotificationCompat.Builder {
val contentIntent = Intent(context, MainActivity::class.java)
val pendingContent =
PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
val addBroadcast = Intent(ADD_BROADCAST).apply {
setPackage(context.packageName)
}
val pendingAdd =
PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
val stopBroadcast = Intent(STOP_BROADCAST)
stopBroadcast.setPackage(context.packageName)
val pendingStop =
PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
return NotificationCompat.Builder(context, CHANNEL_ID_PENDING)
.setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting")
.setContentIntent(pendingContent)
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
.setDeleteIntent(pendingStop)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun getManager(): NotificationManager {
val notificationManager = context.getSystemService(
NotificationManager::class.java
)
val timersChannel = NotificationChannel(
CHANNEL_ID_PENDING, CHANNEL_ID_PENDING, NotificationManager.IMPORTANCE_LOW
)
timersChannel.setSound(null, null)
timersChannel.description = "Progress on rest timers."
notificationManager.createNotificationChannel(timersChannel)
return notificationManager
}
companion object {
const val STOP_BROADCAST = "stop-timer-event"
const val ADD_BROADCAST = "add-timer-event"
const val CHANNEL_ID_PENDING = "Timer"
const val NOTIFICATION_ID_PENDING = 1
}
}
@@ -0,0 +1,19 @@
package com.example.fmassive
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
class MyFlutterPlugin : FlutterPlugin {
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val context = binding.applicationContext
val handler = AlarmModule(context)
channel = MethodChannel(binding.binaryMessenger, "my_channel")
channel.setMethodCallHandler(handler)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>