commit eca9f874a7f1d891947172d1ca9e9507aca4711b parent 2a296910f9dad1f75fbecf40756c4e2c78e01245 Author: MTRNord <MTRNord@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:00:02 +0200 Very initial notifications Diffstat:
13 files changed, 508 insertions(+), 8 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts @@ -70,6 +70,10 @@ dependencies { implementation(libs.androidx.material.icons.extended) implementation(libs.autolinktext) implementation(libs.hilt.android) + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.androidx.hilt.common) + implementation(libs.androidx.hilt.work) + ksp(libs.google.hilt.compiler) ksp(libs.hilt.android.compiler) implementation(libs.hilt.navigation.compose) implementation(libs.maplibre.composeMaterial3) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml @@ -1,9 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <application android:name=".NordicCalendarApp" @@ -15,6 +18,10 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.NordicCalendar"> + <provider + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + tools:node="remove" /> <activity android:name=".MainActivity" android:exported="true" @@ -26,6 +33,9 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <receiver + android:name=".notifications.NotificationReceiver" + android:exported="false" /> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/NordicCalendarApp.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/NordicCalendarApp.kt @@ -5,11 +5,63 @@ import android.app.LocaleManager import android.content.Context import android.os.Build import android.os.LocaleList +import android.util.Log +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.Configuration +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager import dagger.hilt.android.HiltAndroidApp +import space.midnightthoughts.nordiccalendar.background.CalendarSyncWorker +import space.midnightthoughts.nordiccalendar.data.CalendarRepository import java.util.Locale +import java.util.concurrent.TimeUnit +import javax.inject.Inject @HiltAndroidApp -class NordicCalendarApp : Application() +class NordicCalendarApp : Application(), Configuration.Provider { + @Inject + lateinit var workerFactory: HiltWorkerFactory + + @Inject + lateinit var calendarRepository: CalendarRepository + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .build() + + override fun onCreate() { + super.onCreate() + // WorkManager für periodische Kalender-Synchronisation einrichten + Log.d("NordicCalendarApp", "Setting up periodic calendar sync work") + val workRequest = + PeriodicWorkRequestBuilder<CalendarSyncWorker>( + 30, + TimeUnit.MINUTES + ) + .build() + Log.d( + "NordicCalendarApp", + "Enqueuing periodic work request for calendar sync" + ) + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + "calendar_sync", + ExistingPeriodicWorkPolicy.KEEP, + workRequest + ) + // CalendarContentObserver registrieren + Log.d("NordicCalendarApp", "Registering calendar content observer") + calendarRepository.registerCalendarContentObserver(this) + } + + override fun onTerminate() { + super.onTerminate() + // CalendarContentObserver abmelden, um Speicherlecks zu vermeiden + Log.d("NordicCalendarApp", "Unregistering calendar content observer") + calendarRepository.unregisterCalendarContentObserver(this) + } +} fun getCurrentAppLocale(context: Context): Locale { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/background/CalendarSyncWorker.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/background/CalendarSyncWorker.kt @@ -0,0 +1,47 @@ +package space.midnightthoughts.nordiccalendar.background + +import android.content.Context +import android.util.Log +import androidx.hilt.work.HiltWorker +import androidx.work.Worker +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent +import space.midnightthoughts.nordiccalendar.data.CalendarRepository + +@HiltWorker +class CalendarSyncWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted workerParams: WorkerParameters +) : Worker(appContext, workerParams) { + @EntryPoint + @InstallIn(SingletonComponent::class) + interface RepositoryEntryPoint { + fun calendarRepository(): CalendarRepository + } + + override fun doWork(): Result { + Log.d("CalendarSyncWorker", "Starting calendar sync work") + val repo = EntryPointAccessors.fromApplication( + applicationContext, + RepositoryEntryPoint::class.java + ).calendarRepository() + // Zeitraum: jetzt bis 24h später + val now = System.currentTimeMillis() + val tomorrow = now + 24 * 60 * 60 * 1000 + repo.setTimeRange(now, tomorrow) + // Events für diesen Zeitraum holen + val calendars = repo.getCalendars(applicationContext) + val selectedIds = calendars.filter { it.selected }.map { it.id } + val events = repo.getEventsForCalendars(applicationContext, selectedIds, now, tomorrow) + // Reminder-Benachrichtigungen vorher löschen und neu setzen + repo.cancelRemindersForEvents(applicationContext, events) + repo.scheduleRemindersForEvents(applicationContext, events) + Log.d("CalendarSyncWorker", "Calendar sync work completed, ${events.size} events processed") + return Result.success() + } +} diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt @@ -1,6 +1,15 @@ package space.midnightthoughts.nordiccalendar.data +import android.app.AlarmManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.database.ContentObserver +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.provider.CalendarContract +import android.util.Log import androidx.core.content.edit import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope @@ -10,9 +19,11 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch +import space.midnightthoughts.nordiccalendar.notifications.NotificationReceiver import space.midnightthoughts.nordiccalendar.util.Calendar import space.midnightthoughts.nordiccalendar.util.CalendarData import space.midnightthoughts.nordiccalendar.util.Event +import space.midnightthoughts.nordiccalendar.util.Reminder import javax.inject.Inject import javax.inject.Singleton @@ -31,6 +42,8 @@ class CalendarRepository @Inject constructor(@ApplicationContext context: Contex private val _eventsFlow = MutableStateFlow<List<Event>>(emptyList()) val eventsFlow: StateFlow<List<Event>> = _eventsFlow.asStateFlow() + private var calendarContentObserver: ContentObserver? = null + init { loadCalendarsToFlow(context) repoScope.launch { @@ -105,4 +118,160 @@ class CalendarRepository @Inject constructor(@ApplicationContext context: Contex getEventsForCalendars(context, selectedIds, _startMillis.value, _endMillis.value) } } + + // Liefert für eine Liste von Events alle Reminder (Vorlaufzeiten) als Map<EventId, List<Reminder>> + fun getRemindersForEvents( + context: Context, + events: List<Event> + ): Map<Long, List<Reminder>> { + Log.d( + "CalendarRepository", + "Getting reminders for ${events.size} events" + ) + val contentResolver = context.contentResolver + return events.associate { event -> + event.eventId to calendarData.getRemindersForEvent(contentResolver, event.eventId) + } + } + + /** + * Löscht alle Reminder-Alarme für die übergebenen Events. + */ + fun cancelRemindersForEvents(context: Context, events: List<Event>) { + Log.d( + "CalendarRepository", + "Cancelling reminders for ${events.size} events" + ) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val remindersMap = getRemindersForEvents(context, events) + for (event in events) { + val reminders = remindersMap[event.eventId] ?: continue + for (reminder in reminders) { + val intent = Intent(context, NotificationReceiver::class.java).apply { + putExtra("eventId", event.eventId) + putExtra("eventTitle", event.title) + putExtra("eventTime", event.startTime) + putExtra("eventEndTime", event.endTime) + putExtra("eventLocation", event.location) + } + val pendingIntent = PendingIntent.getBroadcast( + context, + (event.eventId + reminder.minutes).toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + alarmManager.cancel(pendingIntent) + } + } + } + + /** + * Setzt für alle Events mit Reminder einen Alarm, der eine Benachrichtigung auslöst. + * Sollte nach jedem Laden der Events aufgerufen werden. + * Vorher werden alle bestehenden Reminder-Alarme für diese Events entfernt. + */ + fun scheduleRemindersForEvents(context: Context, events: List<Event>) { + Log.d( + "CalendarRepository", + "Scheduling reminders for ${events.size} events" + ) + cancelRemindersForEvents(context, events) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val remindersMap = getRemindersForEvents(context, events) + val hasScheduleExactAlarm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + alarmManager.canScheduleExactAlarms() + } else true + for (event in events) { + val reminders = remindersMap[event.eventId] ?: continue + for (reminder in reminders) { + val triggerAtMillis = event.startTime - (reminder.minutes * 60 * 1000) + + if (triggerAtMillis < System.currentTimeMillis()) { + Log.w( + "CalendarRepository", + "Skipping reminder for event ${event.eventId} (${event.title}) " + + "with reminder ${reminder.minutes} minutes, " + + "trigger time $triggerAtMillis is in the past" + ) + continue + } + Log.d( + "CalendarRepository", + "Setting reminder for event ${event.eventId} (${event.title}) with reminder ${reminder.minutes} minutes" + ) + val intent = Intent(context, NotificationReceiver::class.java).apply { + putExtra("eventId", event.eventId) + putExtra("eventTitle", event.title) + putExtra("eventTime", event.startTime) + putExtra("eventEndTime", event.endTime) + putExtra("eventLocation", event.location) + } + val pendingIntent = PendingIntent.getBroadcast( + context, + (event.eventId + reminder.minutes).toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + try { + if (hasScheduleExactAlarm) { + Log.d( + "CalendarRepository", + "Scheduling exact alarm for event ${event.eventId} at $triggerAtMillis" + ) + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtMillis, + pendingIntent + ) + } else { + Log.d( + "CalendarRepository", + "Scheduling non-exact alarm for event ${event.eventId} at $triggerAtMillis" + ) + alarmManager.set( + AlarmManager.RTC_WAKEUP, + triggerAtMillis, + pendingIntent + ) + } + } catch (e: SecurityException) { + e.printStackTrace() + } + } + } + } + + fun registerCalendarContentObserver(context: Context) { + if (calendarContentObserver != null) return // Nur einmal registrieren + val handler = Handler(Looper.getMainLooper()) + calendarContentObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean) { + super.onChange(selfChange) + refreshEvents(context) + } + } + val cr = context.contentResolver + cr.registerContentObserver( + CalendarContract.Events.CONTENT_URI, + true, + calendarContentObserver!! + ) + cr.registerContentObserver( + CalendarContract.Instances.CONTENT_URI, + true, + calendarContentObserver!! + ) + cr.registerContentObserver( + CalendarContract.Reminders.CONTENT_URI, + true, + calendarContentObserver!! + ) + } + + fun unregisterCalendarContentObserver(context: Context) { + calendarContentObserver?.let { + context.contentResolver.unregisterContentObserver(it) + calendarContentObserver = null + } + } } diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/notifications/NotificationHelper.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/notifications/NotificationHelper.kt @@ -0,0 +1,140 @@ +package space.midnightthoughts.nordiccalendar.notifications + +import android.app.Notification.CATEGORY_EVENT +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import space.midnightthoughts.nordiccalendar.MainActivity +import space.midnightthoughts.nordiccalendar.R + +object NotificationHelper { + private const val CHANNEL_ID = "calendar_event_reminders" + private const val CHANNEL_NAME = "Kalender Erinnerungen" + private const val CHANNEL_DESC = "Benachrichtigungen für Kalender-Events mit Erinnerung" + + fun showEventNotification( + context: Context, + eventId: Long, + eventTitle: String, + eventTime: Long, + eventEndTime: Long, + eventLocation: String + ) { + Log.d( + "NotificationHelper", + "showEventNotification: eventId=$eventId, eventTitle=$eventTitle, eventTime=$eventTime" + ) + createNotificationChannel(context) + // Prüfe Berechtigung für Benachrichtigungen (ab Android 13) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission( + context, + android.Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + Log.w( + "NotificationHelper", + "POST_NOTIFICATIONS permission not granted, notification not shown" + ) + // Keine Berechtigung, Notification nicht anzeigen + return + } + } + val intent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val pendingIntent = PendingIntent.getActivity( + context, + eventId.toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + val startCal = java.util.Calendar.getInstance().apply { timeInMillis = eventTime } + val endCal = java.util.Calendar.getInstance().apply { timeInMillis = eventEndTime } + val sameDay = + startCal.get(java.util.Calendar.YEAR) == endCal.get(java.util.Calendar.YEAR) && + startCal.get(java.util.Calendar.DAY_OF_YEAR) == endCal.get(java.util.Calendar.DAY_OF_YEAR) + val timeFormat = android.text.format.DateFormat.getTimeFormat(context) + val dateFormat = android.text.format.DateFormat.getDateFormat(context) + val timeText = if (sameDay) { + "${timeFormat.format(startCal.time)}-${timeFormat.format(endCal.time)}" + } else { + "${dateFormat.format(startCal.time)} ${timeFormat.format(startCal.time)} - ${ + dateFormat.format( + endCal.time + ) + } ${timeFormat.format(endCal.time)}" + } + val contentText = "Beginn: $timeText" + val builder = NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.outline_calendar_clock_24) + .setContentTitle(eventTitle) + .setContentText(contentText) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setCategory(CATEGORY_EVENT) + .setAutoCancel(true) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setColor(ContextCompat.getColor(context, R.color.teal_700)) + // Action für Link oder Adresse + val location = eventLocation + if (android.util.Patterns.WEB_URL.matcher(location).matches()) { + val linkIntent = Intent(Intent.ACTION_VIEW, location.toUri()) + val linkPendingIntent = PendingIntent.getActivity( + context, + (eventId + 1000).toInt(), + linkIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + builder.addAction(0, "Öffnen", linkPendingIntent) + } else if (location.isNotBlank()) { + val mapsIntent = Intent( + Intent.ACTION_VIEW, + ("geo:0,0?q=" + java.net.URLEncoder.encode( + location, + "UTF-8" + )).toUri() + ) + val mapsPendingIntent = PendingIntent.getActivity( + context, + (eventId + 2000).toInt(), + mapsIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + builder.addAction(0, "Route anzeigen", mapsPendingIntent) + } + + try { + with(NotificationManagerCompat.from(context)) { + notify(eventId.toInt(), builder.build()) + Log.d("NotificationHelper", "Notification sent for eventId=$eventId") + } + } catch (e: SecurityException) { + Log.e("NotificationHelper", "SecurityException: ${e.message}") + e.printStackTrace() + } + } + + private fun createNotificationChannel(context: Context) { + Log.d("NotificationHelper", "Creating notification channel: $CHANNEL_ID") + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = CHANNEL_DESC + } + val notificationManager: NotificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } +} diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/notifications/NotificationReceiver.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/notifications/NotificationReceiver.kt @@ -0,0 +1,28 @@ +package space.midnightthoughts.nordiccalendar.notifications + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log + +class NotificationReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val eventId = intent.getLongExtra("eventId", -1) + val eventTitle = intent.getStringExtra("eventTitle") ?: "Kalenderereignis" + val eventTime = intent.getLongExtra("eventTime", 0L) + val eventEndTime = intent.getLongExtra("eventEndTime", 0L) + val eventLocation = intent.getStringExtra("eventLocation") + Log.d( + "NotificationReceiver", + "onReceive: eventId=$eventId, eventTitle=$eventTitle, eventTime=$eventTime, eventEndTime=$eventEndTime, eventLocation=$eventLocation" + ) + NotificationHelper.showEventNotification( + context, + eventId, + eventTitle, + eventTime, + eventEndTime, + eventLocation ?: "" + ) + } +} diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/DayView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/DayView.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.navigation.NavController import kotlinx.coroutines.delay -import space.midnightthoughts.nordiccalendar.Destinations import space.midnightthoughts.nordiccalendar.getCurrentAppLocale import space.midnightthoughts.nordiccalendar.util.Event import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel @@ -260,7 +259,7 @@ fun DayView( isCompact = isCompact, onClick = { navController.navigate( - Destinations.EventDetails.route + "/${event.eventId}?tab=2" + "eventDetails/${event.eventId}?tab=2" ) { launchSingleTop = true restoreState = true diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt @@ -370,7 +370,7 @@ fun EventDetailsView( Text( stringResource( R.string.event_details, - backStackEntry.arguments?.getString("eventId") ?: "" + backStackEntry.arguments?.getLong("eventId") ?: "" ) ) Text( diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt @@ -43,6 +43,9 @@ data class Event( val calendar: Calendar ) +// Reminder-Datenklasse +data class Reminder(val minutes: Int, val method: Int) + // Helper for the android calendar provider/CalendarContract class CalendarData { @@ -349,4 +352,29 @@ class CalendarData { cur?.close() return event } + + // Liefert alle Reminder (Vorlaufzeiten in Minuten) für ein Event + fun getRemindersForEvent(contentResolver: ContentResolver, eventId: Long): List<Reminder> { + val reminders = mutableListOf<Reminder>() + val projection = arrayOf( + CalendarContract.Reminders.MINUTES, + CalendarContract.Reminders.METHOD + ) + val selection = "${CalendarContract.Reminders.EVENT_ID} = ?" + val selectionArgs = arrayOf(eventId.toString()) + val cursor = contentResolver.query( + CalendarContract.Reminders.CONTENT_URI, + projection, + selection, + selectionArgs, + null + ) + while (cursor?.moveToNext() == true) { + val minutes = cursor.getInt(0) + val method = cursor.getInt(1) + reminders.add(Reminder(minutes, method)) + } + cursor?.close() + return reminders + } } \ No newline at end of file diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt @@ -1,6 +1,7 @@ package space.midnightthoughts.nordiccalendar.viewmodels import android.content.Context +import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import space.midnightthoughts.nordiccalendar.data.CalendarRepository import java.time.LocalDate import javax.inject.Inject @@ -45,6 +47,13 @@ class CalendarViewModel @Inject constructor( setDayFromString(dateArg) _dateArg.value = dateArg } + // Reminder-Benachrichtigungen immer aktuell halten (auch beim Init) + viewModelScope.launch { + repository.eventsFlow.collect { events -> + Log.d("CalendarViewModel", "Scheduling reminders for ${events.size} events") + repository.scheduleRemindersForEvents(context, events) + } + } } fun setTab(tab: Int) { @@ -261,9 +270,11 @@ class CalendarViewModel @Inject constructor( fun refreshEvents() { isRefreshing.value = true - repository.refreshEvents(context).apply { - isRefreshing.value = false - } + repository.refreshEvents(context) + // Nach dem Aktualisieren der Events Reminder setzen + val events = repository.eventsFlow.value + repository.scheduleRemindersForEvents(context, events) + isRefreshing.value = false } fun setDay(date: LocalDate) { diff --git a/app/src/main/res/drawable/outline_calendar_clock_24.xml b/app/src/main/res/drawable/outline_calendar_clock_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M200,320L760,320L760,240Q760,240 760,240Q760,240 760,240L200,240Q200,240 200,240Q200,240 200,240L200,320ZM200,320L200,240Q200,240 200,240Q200,240 200,240L200,240Q200,240 200,240Q200,240 200,240L200,320ZM200,880Q167,880 143.5,856.5Q120,833 120,800L120,240Q120,207 143.5,183.5Q167,160 200,160L240,160L240,80L320,80L320,160L640,160L640,80L720,80L720,160L760,160Q793,160 816.5,183.5Q840,207 840,240L840,467Q821,458 801,452Q781,446 760,443L760,400L200,400L200,800Q200,800 200,800Q200,800 200,800L452,800Q459,822 468.5,842Q478,862 491,880L200,880ZM720,920Q637,920 578.5,861.5Q520,803 520,720Q520,637 578.5,578.5Q637,520 720,520Q803,520 861.5,578.5Q920,637 920,720Q920,803 861.5,861.5Q803,920 720,920ZM787,815L815,787L740,712L740,600L700,600L700,728L787,815Z"/> + +</vector> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml @@ -20,6 +20,9 @@ ktorVersion = "3.2.3" preferenceVersion = "2.1.0" accessibilityTestVersion = "1.8.3" testRulesVersion = "1.6.1" +workRuntimeKtxVersion = "2.10.3" +hiltCommonVersion = "1.2.0" +hiltWorkVersion = "1.2.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -53,6 +56,10 @@ ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version compose-preference = { group = "me.zhanghai.compose.preference", name = "preference", version.ref = "preferenceVersion" } androidx-compose-ui-accessibility-test = { group = "androidx.compose.ui", name = "ui-test-junit4-accessibility", version.ref = "accessibilityTestVersion" } androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "testRulesVersion" } +androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtxVersion" } +androidx-hilt-common = { group = "androidx.hilt", name = "hilt-common", version.ref = "hiltCommonVersion" } +androidx-hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltWorkVersion" } +google-hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }