From 29226dd93e9dfcf72ec80e476331d1bcc43c6e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 20 May 2020 15:11:01 +0200 Subject: [PATCH] Add notification about upcoming lesson (#578) --- .../timetable/TimetableRepositoryTest.kt | 20 ++- app/src/main/AndroidManifest.xml | 2 + .../github/wulkanowy/data/RepositoryModule.kt | 2 + .../preferences/PreferencesRepository.kt | 4 + .../timetable/TimetableRepository.kt | 10 +- .../io/github/wulkanowy/di/BindingModule.kt | 4 + .../wulkanowy/services/ServicesModule.kt | 11 ++ .../alarm/TimetableNotificationReceiver.kt | 117 ++++++++++++++++++ .../TimetableNotificationSchedulerHelper.kt | 109 ++++++++++++++++ .../sync/channels/UpcomingLessonsChannel.kt | 31 +++++ .../ui/modules/settings/SettingsPresenter.kt | 6 +- .../github/wulkanowy/utils/TimeExtension.kt | 8 ++ .../res/drawable-hdpi/ic_stat_timetable.png | Bin 0 -> 312 bytes .../res/drawable-mdpi/ic_stat_timetable.png | Bin 0 -> 275 bytes .../res/drawable-xhdpi/ic_stat_timetable.png | Bin 0 -> 358 bytes .../res/drawable-xxhdpi/ic_stat_timetable.png | Bin 0 -> 459 bytes .../drawable-xxxhdpi/ic_stat_timetable.png | Bin 0 -> 659 bytes app/src/main/res/values-pl/strings.xml | 5 + .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/xml/scheme_preferences.xml | 5 + 22 files changed, 333 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt create mode 100644 app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_stat_timetable.png diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt index fdf193a2..75f2f0b8 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt @@ -8,12 +8,15 @@ import androidx.test.filters.SdkSuppress import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy import io.github.wulkanowy.data.repositories.getStudent +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.sdk.Sdk import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.reactivex.Single import org.junit.After import org.junit.Before @@ -34,11 +37,17 @@ class TimetableRepositoryTest { .strategy(TestInternetObservingStrategy()) .build() + @MockK + private lateinit var studentMock: Student + private val student = getStudent() @MockK private lateinit var semesterMock: Semester + @MockK + private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper + private lateinit var timetableRemote: TimetableRemote private lateinit var timetableLocal: TimetableLocal @@ -52,10 +61,17 @@ class TimetableRepositoryTest { timetableLocal = TimetableLocal(testDb.timetableDao) timetableRemote = TimetableRemote(mockSdk) + every { timetableNotificationSchedulerHelper.scheduleNotifications(any(), any()) } returns mockk() + every { timetableNotificationSchedulerHelper.cancelScheduled(any(), any()) } returns mockk() + + every { studentMock.studentId } returns 1 + every { studentMock.studentName } returns "Jan Kowalski" + every { semesterMock.studentId } returns 1 every { semesterMock.diaryId } returns 2 every { semesterMock.schoolYear } returns 2019 every { semesterMock.semesterId } returns 1 + every { mockSdk.switchDiary(any(), any()) } returns mockSdk } @@ -80,7 +96,7 @@ class TimetableRepositoryTest { createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F") )) - val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) + val lessons = TimetableRepository(settings, timetableLocal, timetableRemote, timetableNotificationSchedulerHelper) .getTimetable(student, semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true) .blockingGet() @@ -126,7 +142,7 @@ class TimetableRepositoryTest { createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) )) - val lessons = TimetableRepository(settings, timetableLocal, timetableRemote) + val lessons = TimetableRepository(settings, timetableLocal, timetableRemote, timetableNotificationSchedulerHelper) .getTimetable(student, semesterMock, LocalDate.of(2019, 12, 23), LocalDate.of(2019, 12, 25), true) .blockingGet() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4dd70721..4ec2f781 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,6 +92,8 @@ android:resource="@xml/provider_widget_lucky_number" /> + + > { @@ -31,8 +33,8 @@ class TimetableRepository @Inject constructor( local.getTimetable(semester, monday, friday) .toSingle(emptyList()) .doOnSuccess { old -> - local.deleteTimetable(old.uniqueSubtract(new)) - local.saveTimetable(new.uniqueSubtract(old).map { item -> + local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) + local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> item.also { new -> old.singleOrNull { new.start == it.start }?.let { old -> return@map new.copy( @@ -45,7 +47,7 @@ class TimetableRepository @Inject constructor( } }.flatMap { local.getTimetable(semester, monday, friday).toSingle(emptyList()) - }).map { list -> list.filter { it.date in start..end } } + }).map { list -> list.filter { it.date in start..end }.also { schedulerHelper.scheduleNotifications(it, student) } } } } } diff --git a/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt b/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt index ba8c78d3..1b462964 100644 --- a/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/BindingModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginModule import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity @@ -48,4 +49,7 @@ internal abstract class BindingModule { @ContributesAndroidInjector abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider + + @ContributesAndroidInjector + abstract fun bindTimetableNotificationReceiver(): TimetableNotificationReceiver } diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt index c7c573e2..b87f0e68 100644 --- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt +++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt @@ -1,7 +1,9 @@ package io.github.wulkanowy.services +import android.app.AlarmManager import android.content.Context import androidx.core.app.NotificationManagerCompat +import androidx.core.content.getSystemService import androidx.work.WorkManager import com.squareup.inject.assisted.dagger2.AssistedModule import dagger.Binds @@ -15,6 +17,7 @@ import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel import io.github.wulkanowy.services.sync.channels.NewGradesChannel import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel +import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel import io.github.wulkanowy.services.sync.channels.PushChannel import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceWork @@ -46,6 +49,10 @@ abstract class ServicesModule { @Singleton @Provides fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context) + + @Singleton + @Provides + fun provideAlarmManager(context: Context): AlarmManager = context.getSystemService()!! } @ContributesAndroidInjector @@ -126,4 +133,8 @@ abstract class ServicesModule { @Binds @IntoSet abstract fun providePushChannel(channel: PushChannel): Channel + + @Binds + @IntoSet + abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt new file mode 100644 index 00000000..0130f467 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -0,0 +1,117 @@ +package io.github.wulkanowy.services.alarm + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Build.VERSION_CODES.N +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dagger.android.AndroidInjection +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.toLocalDateTime +import timber.log.Timber +import javax.inject.Inject + +class TimetableNotificationReceiver : BroadcastReceiver() { + + @Inject + lateinit var studentRepository: StudentRepository + + @Inject + lateinit var schedulers: SchedulersProvider + + companion object { + const val NOTIFICATION_TYPE_CURRENT = 1 + const val NOTIFICATION_TYPE_UPCOMING = 2 + const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3 + + const val NOTIFICATION_ID = "id" + + const val STUDENT_NAME = "student_name" + const val STUDENT_ID = "student_id" + const val LESSON_TYPE = "type" + const val LESSON_TITLE = "title" + const val LESSON_ROOM = "room" + const val LESSON_NEXT_TITLE = "next_title" + const val LESSON_NEXT_ROOM = "next_room" + const val LESSON_START = "start_timestamp" + const val LESSON_END = "end_timestamp" + } + + @SuppressLint("CheckResult") + override fun onReceive(context: Context, intent: Intent) { + Timber.d("Receiving intent... ${intent.toUri(0)}") + AndroidInjection.inject(this, context) + + studentRepository.getCurrentStudent(false) + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .subscribe({ + val studentId = intent.getIntExtra(STUDENT_ID, 0) + if (it.studentId == studentId) prepareNotification(context, intent) + else Timber.d("Notification studentId($studentId) differs from current(${it.studentId})") + }, { Timber.e(it) }) + } + + private fun prepareNotification(context: Context, intent: Intent) { + val type = intent.getIntExtra(LESSON_TYPE, 0) + val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) + + if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) { + return NotificationManagerCompat.from(context).cancel(notificationId) + } + + val studentId = intent.getIntExtra(STUDENT_ID, 0) + val studentName = intent.getStringExtra(STUDENT_NAME) + + val subject = intent.getStringExtra(LESSON_TITLE) + val room = intent.getStringExtra(LESSON_ROOM) + + val start = intent.getLongExtra(LESSON_START, 0) + val end = intent.getLongExtra(LESSON_END, 0) + + val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE) + val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM) + + Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId") + + showNotification(context, notificationId, studentName, + if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start, + context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")), + nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) } + ) + } + + private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) { + NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(next) + .setAutoCancel(false) + .setOngoing(true) + .setWhen(countDown) + .apply { + if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true) + } + .setTimeoutAfter(timeout) + .setSmallIcon(R.drawable.ic_stat_timetable) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setStyle(NotificationCompat.InboxStyle().also { + it.setSummaryText(studentName) + it.addLine(next) + }) + .setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id, + MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT)) + .build() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt new file mode 100644 index 00000000..5374c476 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -0,0 +1,109 @@ +package io.github.wulkanowy.services.alarm + +import android.app.AlarmManager +import android.app.AlarmManager.RTC_WAKEUP +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_CANCEL_CURRENT +import android.content.Context +import android.content.Intent +import androidx.core.app.AlarmManagerCompat +import androidx.core.app.NotificationManagerCompat +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_END +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_ROOM +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_TITLE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_ROOM +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_START +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TITLE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TYPE +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_ID +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_CURRENT +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID +import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.toTimestamp +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalDateTime.now +import timber.log.Timber +import javax.inject.Inject + +class TimetableNotificationSchedulerHelper @Inject constructor( + private val context: Context, + private val alarmManager: AlarmManager, + private val preferencesRepository: PreferencesRepository +) { + + private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt() + + private fun getUpcomingLessonTime(index: Int, day: List, lesson: Timetable): LocalDateTime { + return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) + } + + fun cancelScheduled(lessons: List, studentId: Int = 1) { + lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> + val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) + cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId)) + cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId)) + + Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") + } + } + + private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { + if (now() in range) cancelNotification() + alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_CANCEL_CURRENT)) + } + + fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) + + fun scheduleNotifications(lessons: List, student: Student) { + if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId) + + lessons.groupBy { it.date } + .map { it.value.sortedBy { lesson -> lesson.start } } + .map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } } + .map { day -> + day.forEachIndexed { index, lesson -> + val intent = createIntent(student, lesson, day.getOrNull(index + 1)) + + if (lesson.start > now()) { + scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson)) + } + + if (lesson.end > now()) { + scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start) + if (day.lastIndex == index) { + scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end) + } + } + } + } + } + + private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { + return Intent(context, TimetableNotificationReceiver::class.java).apply { + putExtra(STUDENT_ID, student.studentId) + putExtra(STUDENT_NAME, student.studentName) + putExtra(LESSON_ROOM, lesson.room) + putExtra(LESSON_START, lesson.start.toTimestamp()) + putExtra(LESSON_END, lesson.end.toTimestamp()) + putExtra(LESSON_TITLE, lesson.subject) + putExtra(LESSON_NEXT_TITLE, nextLesson?.subject) + putExtra(LESSON_NEXT_ROOM, nextLesson?.room) + } + } + + private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) { + AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(), + PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { + it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) + it.putExtra(LESSON_TYPE, notificationType) + }, FLAG_CANCEL_CURRENT) + ) + Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt new file mode 100644 index 00000000..a292c8b5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification.VISIBILITY_PUBLIC +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class UpcomingLessonsChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "lesson_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_upcoming_lessons), IMPORTANCE_DEFAULT).apply { + lockscreenVisibility = VISIBILITY_PUBLIC + setShowBadge(false) + enableVibration(false) + } + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index c8545ac0..09fc2d70 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -4,6 +4,7 @@ import androidx.work.WorkInfo import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -20,6 +21,7 @@ class SettingsPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val preferencesRepository: PreferencesRepository, + private val timetableNotificationHelper: TimetableNotificationSchedulerHelper, private val analytics: FirebaseAnalyticsHelper, private val syncManager: SyncManager, private val chuckerCollector: ChuckerCollector, @@ -36,17 +38,17 @@ class SettingsPresenter @Inject constructor( fun onSharedPreferenceChanged(key: String) { Timber.i("Change settings $key") - with(preferencesRepository) { + preferencesRepository.apply { when (key) { serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() } servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true) isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable appThemeKey -> view?.recreateView() + isUpcomingLessonsNotificationsEnableKey -> if (!isUpcomingLessonsNotificationsEnable) timetableNotificationHelper.cancelNotification() appLanguageKey -> view?.run { updateLanguage(if (appLanguage == "system") appInfo.systemLanguage else appLanguage) recreateView() } - else -> Unit } } analytics.logEvent("setting_changed", "name" to key) diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index a91f823f..8d022fc5 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -4,9 +4,13 @@ import org.threeten.bp.DayOfWeek.FRIDAY import org.threeten.bp.DayOfWeek.MONDAY import org.threeten.bp.DayOfWeek.SATURDAY import org.threeten.bp.DayOfWeek.SUNDAY +import org.threeten.bp.Instant.ofEpochMilli import org.threeten.bp.LocalDate import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalDateTime.ofInstant import org.threeten.bp.Month +import org.threeten.bp.ZoneId +import org.threeten.bp.ZoneOffset import org.threeten.bp.format.DateTimeFormatter.ofPattern import org.threeten.bp.format.TextStyle.FULL_STANDALONE import org.threeten.bp.temporal.TemporalAdjusters.firstInMonth @@ -18,6 +22,10 @@ private const val DATE_PATTERN = "dd.MM.yyyy" fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate = LocalDate.parse(this, ofPattern(format)) +fun LocalDateTime.toTimestamp() = atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toInstant().toEpochMilli() + +fun Long.toLocalDateTime() = ofInstant(ofEpochMilli(this), ZoneId.systemDefault()) + fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) diff --git a/app/src/main/res/drawable-hdpi/ic_stat_timetable.png b/app/src/main/res/drawable-hdpi/ic_stat_timetable.png new file mode 100644 index 0000000000000000000000000000000000000000..201419d5d48501c657d592ea41c27763c5e6eb7f GIT binary patch literal 312 zcmV-80muG{P)p3-bypiuo>p^LPeGgqjf!K!moOi)p8!4+zErZjvw^m>OZWGn6nHS|z4MdREK z4OfV6{7eI3ODNS`;M5zTNb|ag{8;KCp0d6>lz1z&>3KZ}KX>TgLN^+$HLr_r)YNp+YJcc9@) z3cf>%1(X1yELa9j-=yGWq*y@nIGBp22_KkBi3K%4e3byMA=?6a1d$PrBEp^4ad4Gd z7I@((7P6>f0WIRd3W$GEP*i~QTH&*RABfjN6_N@-dRF7B0b!Q#0I>!cMFmI?&+uzJ Z007MEy7feVSabjY002ovPDHLkV1gACY-j)g literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png b/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png new file mode 100644 index 0000000000000000000000000000000000000000..7264bd92a947d264f2b576b17645a1346557cf53 GIT binary patch literal 358 zcmV-s0h#`ZP)d;9zOt??m>XClLts&bx~04kZE$W1wf@ zg7A#a1qF&E5EwrrvLMJ(z(pufD1qFc0bZjN_?5t$B1t?7{G&ia@FZr^;31|!4zJN~ z&Ong&?}3N{6yPmHfscmG!NcJbnd+WP1Sa6(@Ok(Q#u9-x*nzu)#jDOOXh{UB`VI9) z?}n|{>#A!Fx}YLGR29(FBEE$);Oj2cS>|7=3{&vD05k7d?=*zzl>h($07*qoM6N<$ Ef`ze{s{jB1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png b/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png new file mode 100644 index 0000000000000000000000000000000000000000..1fb37b092c4c26e76a349ab679d42edf8797c551 GIT binary patch literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V4URX;uunK>+S6QUWWrD+?j8A zZkYV&(VJD>>uw%lFL?i8$?W9i9sLSB$_hkXSMbGddd>ZVedRl)Z3~)Y*X`#nzr->1 zQ)~50%YtV$wwXow3MY+#Ml;NicpjtIJzq+0{R)GA%L(!y#M_RqUZfz<$lO0FZ+(#yUH8=SP~)FB>C+mkM8D7ZCm;P!;!XIS$yL)DPjzh$a@+HE zMR^>PV21-smq9?50FYL?5W?#9GO#&yUM_PWD~X-KRB{9p0ZY zC1H+!rDC}b^Uw1OzCF!uYK%L?8MlGQM0@&^wmnn$AMWb=Iqz}$n^UDbHPY9Fx3%S* v%H64cTX4U_RX*cik$<=6_40s%?${&gU0d$GUCOb13yACK>gTe~DWM4f-M7mX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_timetable.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_timetable.png new file mode 100644 index 0000000000000000000000000000000000000000..a95cc4f5d9b0dbfcf5513827f0309aa1e2bf89e7 GIT binary patch literal 659 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V2bi|aSW-r^>(hUcZ{Qi+w=|= z7SBXMj%-2QOYT7$9hoAEQ5^61H}VT zo}B)Hmu2E!mn((o=Nd0_-YY-<^xc_V-)Eg>1X=_FN7{??mbJ}g>|4J1n&DB21!AoX z$AedtIDciNZg~@Q zH#zs6D74h=7Swg_HoLsz?2lV%c_&~IeuclDVSCvhefvfa4NwB`boFyt=akR{00hbj ALI3~& literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index bff42292..e6fe6da6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -121,6 +121,9 @@ Godziny Zmiany Brak lekcji w tym dniu + Teraz: %s + Za chwilę: %s + Później: %s Lekcje zrealizowane Zobacz lekcje zrealizowane @@ -319,6 +322,7 @@ Język aplikacji Powiadomienia Pokazuj powiadomienia + Pokazuj powiadomienia o następnych lekcjach Pokazuj powiadomienia debugowania Synchronizacja Automatyczna aktualizacja @@ -344,6 +348,7 @@ Nowe wiadomości Nowe uwagi Powiadomienia push + Nadchodzące lekcje Debugowanie Czarny diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 29e8751e..c8704a50 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -13,6 +13,7 @@ 60 false true + false false 0.33 0.33 diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 6d683f3f..1d43f79f 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -14,6 +14,7 @@ services_disable_wifi_only services_force_sync notifications_enable + notifications_upcoming_lessons_enable notification_debug grade_modifier_plus grade_modifier_minus diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a837c46..bfaece67 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -130,6 +130,9 @@ Hours Changes No lessons this day + Now: %s + Next: %s + Later: %s @@ -356,6 +359,7 @@ Notifications Show notifications + Show upcoming lesson notifications Show debug notifications Synchronization @@ -386,6 +390,7 @@ New messages New notes Push notifications + Upcoming lessons Debug diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml index b4adabb9..d890fdb2 100644 --- a/app/src/main/res/xml/scheme_preferences.xml +++ b/app/src/main/res/xml/scheme_preferences.xml @@ -96,6 +96,11 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_notifications_enable" app:title="@string/pref_notify_switch" /> +