package uk.beads.manager import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.media.AudioAttributes import android.media.MediaPlayer import android.media.RingtoneManager import android.net.Uri import android.os.Build import android.os.IBinder import android.os.VibrationEffect import android.os.Vibrator import android.os.VibratorManager import android.util.Log import androidx.core.app.NotificationCompat class OrderPollingService : Service() { private var polling = true private var lastCheck: String? = null private var lastOrderIds = mutableSetOf() private var player: MediaPlayer? = null override fun onCreate() { super.onCreate() createNotificationChannel() startForeground(NOTIFICATION_ID, buildForegroundNotification()) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { polling = true Thread { pollLoop() }.start() return START_STICKY } override fun onBind(intent: Intent?): IBinder? = null override fun onDestroy() { polling = false player?.release() player = null super.onDestroy() } private fun pollLoop() { while (polling) { try { val result = ApiClient.pollOrders(this, lastCheck) if (result.success && result.orders.isNotEmpty()) { for (order in result.orders) { if (order.id !in lastOrderIds) { lastOrderIds.add(order.id) playAlert() showOrderNotification(order) } } } lastCheck = result.serverTime } catch (e: Exception) { Log.w("OrderPolling", "Poll failed: ${e.message}") } if (polling) { Thread.sleep(POLL_INTERVAL_MS) } } } private fun playAlert() { try { val alarmUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) ?: RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) player?.release() player = MediaPlayer().apply { setAudioAttributes( AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ALARM) .build() ) setDataSource(this@OrderPollingService, alarmUri) isLooping = false setVolume(1.0f, 1.0f) prepare() start() } } catch (e: Exception) { Log.w("OrderPolling", "Play alert failed: ${e.message}") } vibrate() } private fun vibrate() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val vibrator = getSystemService(VIBRATOR_MANAGER_SERVICE) as? VibratorManager vibrator?.defaultVibrator?.vibrate( VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE) ) } else { @Suppress("DEPRECATION") val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator vibrator?.vibrate(VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE)) } } catch (_: Exception) {} } private fun showOrderNotification(order: OrderDto) { val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val detailIntent = Intent(this, OrderDetailActivity::class.java).apply { putExtra("order_id", order.id) } val pending = PendingIntent.getActivity( this, order.id, detailIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_dialog_info) .setContentTitle("New Order! #${order.orderNumber}") .setContentText("${order.customer.firstName} ${order.customer.lastName} — £${"%.2f".format(order.total)}") .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) .setContentIntent(pending) .build() manager.notify(order.id, notification) } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, "Order Alerts", NotificationManager.IMPORTANCE_HIGH ).apply { description = "Loud alerts when new orders arrive" enableVibration(true) setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM), null) } val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager manager.createNotificationChannel(channel) } } private fun buildForegroundNotification(): Notification { val pending = PendingIntent.getActivity( this, 0, Intent(this, OrdersActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_dialog_info) .setContentTitle("Listening for orders") .setContentText("New order alerts active") .setOngoing(true) .setContentIntent(pending) .build() } companion object { private const val CHANNEL_ID = "order_polling" private const val NOTIFICATION_ID = 1001 private const val POLL_INTERVAL_MS = 15000L fun start(context: Context) { val intent = Intent(context, OrderPollingService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent) } else { context.startService(intent) } } fun stop(context: Context) { context.stopService(Intent(context, OrderPollingService::class.java)) } } }