diff --git a/app/src/main/java/com/pomo/myapplication/MainActivity.kt b/app/src/main/java/com/pomo/myapplication/MainActivity.kt index d5906dd487ee73a88235a45ba12b45d1ccd274c7..da2135fd49460a4e51e3bf11e9a57be8cd6bb930 100644 --- a/app/src/main/java/com/pomo/myapplication/MainActivity.kt +++ b/app/src/main/java/com/pomo/myapplication/MainActivity.kt @@ -1,6 +1,6 @@ package com.pomo.myapplication -import android.Manifest +import android.Manifest.permission.POST_NOTIFICATIONS import android.content.pm.PackageManager import android.os.Bundle import androidx.activity.ComponentActivity @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.material3.Button @@ -19,6 +20,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -28,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.app.ActivityCompat import com.pomo.myapplication.ui.theme.MyApplicationTheme import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -37,6 +40,8 @@ import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { + private val postNotificationsPermission = POST_NOTIFICATIONS + private lateinit var notificationHelper: NotificationHelper private var timeLeftForNotification: String? = null @@ -69,11 +74,12 @@ class MainActivity : ComponentActivity() { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_CODE) { if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { - // Berechtigung erteilt, zeige die Benachrichtigung an - timeLeftForNotification?.let { notificationHelper.showNotification(it) } + // Permission granted, show the notification if needed + timeLeftForNotification?.let { + notificationHelper.updateNotification() + } } else { - // Berechtigung verweigert, handle dies entsprechend - // Zeige z.B. eine Snackbar oder Toast, um den Benutzer zu informieren + // Permission denied, handle accordingly } } } @@ -82,25 +88,38 @@ class MainActivity : ComponentActivity() { fun TimerScreen() { var timeLeftInMillis by remember { mutableLongStateOf(25 * 60 * 1000L) } var timerRunning by remember { mutableStateOf(false) } + var customMinutes by remember { mutableIntStateOf(0) } val timeFormatted = remember(timeLeftInMillis) { val minutes = (timeLeftInMillis / 1000) / 60 val seconds = (timeLeftInMillis / 1000) % 60 String.format("%02d:%02d", minutes, seconds) } + var showDialog by remember { mutableStateOf(false) } + var inputMinutes by remember { mutableStateOf("") } + var job: Job? by remember { mutableStateOf(null) } fun startTimer() { job?.cancel() job = CoroutineScope(Dispatchers.Main).launch { timerRunning = true + notificationHelper.startTimerCountdown(timeLeftInMillis) while (timeLeftInMillis > 0 && timerRunning) { delay(1000L) timeLeftInMillis -= 1000L + // Update notification with the remaining time + timeLeftForNotification = timeFormatted + if (ActivityCompat.checkSelfPermission( + this@MainActivity, + postNotificationsPermission + ) == PackageManager.PERMISSION_GRANTED + ) { + notificationHelper.updateNotification() + } } if (timeLeftInMillis <= 0) { timerRunning = false - // Timer abgelaufen, Benachrichtigung anzeigen notificationHelper.showTimerFinishedNotification() } } @@ -121,8 +140,17 @@ class MainActivity : ComponentActivity() { job?.cancel() timeLeftInMillis = minutes * 60 * 1000L timerRunning = false + notificationHelper.cancelNotification() } + fun setCustomTimer() { + val minutes = inputMinutes.toIntOrNull() ?: 0 + if (minutes > 0) { + customMinutes = minutes + setTimer(customMinutes) + showDialog = false + } + } Column( modifier = Modifier.fillMaxSize(), @@ -134,9 +162,19 @@ class MainActivity : ComponentActivity() { Row { Button( onClick = { - startTimer() - timeLeftForNotification = timeFormatted - notificationHelper.showNotification(timeFormatted) + if (ActivityCompat.checkSelfPermission( + this@MainActivity, + postNotificationsPermission + ) == PackageManager.PERMISSION_GRANTED + ) { + startTimer() + } else { + ActivityCompat.requestPermissions( + this@MainActivity, + arrayOf(postNotificationsPermission), + PERMISSION_REQUEST_CODE + ) + } }, colors = ButtonDefaults.buttonColors( containerColor = Color(0xFF00AA07), @@ -163,16 +201,13 @@ class MainActivity : ComponentActivity() { } } - Spacer(modifier = Modifier.height(20.dp)) Row { - - Button( onClick = { setTimer(5) }, colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF6650a4), // Grün - contentColor = Color.White // Weißer Text + containerColor = Color(0xFF6650a4), + contentColor = Color.White ) ) { Text(text = "5 min") @@ -182,8 +217,8 @@ class MainActivity : ComponentActivity() { Button( onClick = { setTimer(15) }, colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF6650a4), // Grün - contentColor = Color.White // Weißer Text + containerColor = Color(0xFF6650a4), + contentColor = Color.White ) ) { Text(text = "15 min") @@ -193,15 +228,66 @@ class MainActivity : ComponentActivity() { Button( onClick = { setTimer(25) }, colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF6650a4), // Grün - contentColor = Color.White // Weißer Text + containerColor = Color(0xFF6650a4), + contentColor = Color.White ) ) { Text(text = "25 min") } } + + Spacer(modifier = Modifier.height(20.dp)) + + Spacer(modifier = Modifier.height(8.dp)) + Button(onClick = { showDialog = true }) { + Text(text = "Set Custom Timer") + } + + if (showDialog) { + CustomTimerDialog( + inputMinutes = inputMinutes, + onMinutesChange = { inputMinutes = it }, + onConfirm = { setCustomTimer() }, + onDismiss = { showDialog = false } + ) + } } } +} + + @Composable + fun CustomTimerDialog( + inputMinutes: String, + onMinutesChange: (String) -> Unit, + onConfirm: () -> Unit, + onDismiss: () -> Unit + ) { + androidx.compose.material3.AlertDialog( + onDismissRequest = onDismiss, + title = { Text(text = "Set Custom Timer") }, + text = { + Column { + Text(text = "Enter the number of minutes:") + Spacer(modifier = Modifier.height(8.dp)) + androidx.compose.material3.TextField( + value = inputMinutes, + onValueChange = onMinutesChange, + modifier = Modifier.fillMaxWidth() + ) + } + }, + confirmButton = { + Button(onClick = onConfirm) { + Text(text = "OK") + } + }, + dismissButton = { + Button(onClick = onDismiss) { + Text(text = "Cancel") + } + } + ) + } @Composable @@ -212,6 +298,7 @@ class MainActivity : ComponentActivity() { ) } + @Preview(showBackground = true) @Composable fun TimerScreenPreview() { @@ -219,4 +306,9 @@ class MainActivity : ComponentActivity() { TimerScreen() } } + +@Composable +fun TimerScreen() { + TODO("Not yet implemented") } + diff --git a/app/src/main/java/com/pomo/myapplication/NotificationUtils.kt b/app/src/main/java/com/pomo/myapplication/NotificationUtils.kt index 8b8f72fa9d5da6107b976a6f95205c7b29ebb086..d2d1cbefdad9e43cedcc9283501d4e88b0d8e34d 100644 --- a/app/src/main/java/com/pomo/myapplication/NotificationUtils.kt +++ b/app/src/main/java/com/pomo/myapplication/NotificationUtils.kt @@ -10,6 +10,8 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.Build +import android.os.Handler +import android.os.Looper import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -17,12 +19,14 @@ import androidx.core.app.NotificationManagerCompat class NotificationHelper(private val context: Context) { companion object { - // Konstante für die Berechtigungsanfrage private const val PERMISSION_REQUEST_CODE = 1001 + private const val CHANNEL_ID = "timer_channel" + private const val NOTIFICATION_ID = 1 } - private val CHANNEL_ID = "timer_channel" - private val NOTIFICATION_ID = 1 + private val handler = Handler(Looper.getMainLooper()) + private var timeLeft = "00:00" + private var isTimerRunning = false init { createNotificationChannel() @@ -42,55 +46,70 @@ class NotificationHelper(private val context: Context) { } } - fun showNotification(timeLeft: String) { - // Überprüfen, ob die Berechtigung zum Posten von Benachrichtigungen erteilt wurde - if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.POST_NOTIFICATIONS - ) != PackageManager.PERMISSION_GRANTED - ) { - // Berechtigung ist nicht erteilt, daher fordern wir sie an - if (context is Activity) { - ActivityCompat.requestPermissions( - context, - arrayOf(Manifest.permission.POST_NOTIFICATIONS), - PERMISSION_REQUEST_CODE - ) + fun startTimerCountdown(durationInMillis: Long) { + timeLeft = formatTime(durationInMillis) + isTimerRunning = true + updateNotification() // No sound during countdown + + handler.postDelayed(object : Runnable { + var remainingTime = durationInMillis + + override fun run() { + if (isTimerRunning && remainingTime > 0) { + remainingTime -= 1000 // Decrease by 1 second + timeLeft = formatTime(remainingTime) + handler.postDelayed(this, 1000) // Schedule next update + } else { + isTimerRunning = false + showTimerFinishedNotification() // Show finished notification with sound + } } - return // Beende die Funktion, wenn die Berechtigung nicht erteilt ist - } + }, 0) + } - // Erstellen des Intents und PendingIntents - val intent = Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) - - // Erstellen und Konfigurieren der Benachrichtigung - val notification: Notification = NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_launcher_foreground) - .setContentTitle("Timer läuft") - .setContentText("Noch verbleibende Zeit: $timeLeft") - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - .build() + fun updateNotification() { + if (!hasNotificationPermission()) return + + val notification = buildNotification( + title = "Timer läuft", + contentText = "Noch verbleibende Zeit: $timeLeft", + ) + + showNotification(notification) + } + + fun showTimerFinishedNotification() { + if (!hasNotificationPermission()) return + + val notification = buildNotification( + title = "Timer abgelaufen", + contentText = "Der Timer ist abgelaufen!", + ) + + showNotification(notification) + } - // Anzeigen der Benachrichtigung + fun cancelNotification() { + isTimerRunning = false with(NotificationManagerCompat.from(context)) { - notify(NOTIFICATION_ID, notification) + cancel(NOTIFICATION_ID) } } - fun showTimerFinishedNotification() { - // Überprüfen, ob die Berechtigung zum Posten von Benachrichtigungen erteilt wurde - if (ActivityCompat.checkSelfPermission( + private fun formatTime(durationInMillis: Long): String { + val minutes = (durationInMillis / 1000) / 60 + val seconds = (durationInMillis / 1000) % 60 + return String.format("%02d:%02d", minutes, seconds) + } + + private fun hasNotificationPermission(): Boolean { + return if (ActivityCompat.checkSelfPermission( context, Manifest.permission.POST_NOTIFICATIONS - ) != PackageManager.PERMISSION_GRANTED + ) == PackageManager.PERMISSION_GRANTED ) { - // Berechtigung ist nicht erteilt, handle dies entsprechend - // Du kannst hier eine Anfrage starten oder den Benutzer informieren + true + } else { if (context is Activity) { ActivityCompat.requestPermissions( context, @@ -98,34 +117,37 @@ class NotificationHelper(private val context: Context) { PERMISSION_REQUEST_CODE ) } - return + false } + } - // Erstellen des Intents und PendingIntents + private fun buildNotification(title: String, contentText: String): Notification { val intent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } - val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) - - // Erstellen und Konfigurieren der Benachrichtigung - val notification: Notification = NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_launcher_foreground) - .setContentTitle("Timer abgelaufen") - .setContentText("Der Timer ist abgelaufen!") - .setPriority(NotificationCompat.PRIORITY_DEFAULT) + val pendingIntent: PendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + + val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_launcher_foreground) // Ensure you have this drawable resource + .setContentTitle(title) + .setContentText(contentText) + .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) .setAutoCancel(true) - .build() - // Anzeigen der Benachrichtigung - with(NotificationManagerCompat.from(context)) { - notify(NOTIFICATION_ID, notification) - } + + return notificationBuilder.build() } - fun cancelNotification() { - with(NotificationManagerCompat.from(context)) { - cancel(NOTIFICATION_ID) + private fun showNotification(notification: Notification) { + try { + with(NotificationManagerCompat.from(context)) { + notify(NOTIFICATION_ID, notification) + } + } catch (e: SecurityException) { + e.printStackTrace() + // Optionally, you could also show a toast or log the issue } } }