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 fdf193a26..75f2f0b83 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 4dd70721e..4ec2f7816 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 ba8c78d3f..1b462964d 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 c7c573e27..b87f0e683 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 000000000..0130f4673
--- /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 000000000..5374c4767
--- /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 000000000..a292c8b53
--- /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 c8545ac0e..09fc2d707 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 a91f823fa..8d022fc5d 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 000000000..201419d5d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stat_timetable.png b/app/src/main/res/drawable-mdpi/ic_stat_timetable.png
new file mode 100644
index 000000000..dcfe95f26
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_timetable.png differ
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 000000000..7264bd92a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_timetable.png differ
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 000000000..1fb37b092
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stat_timetable.png differ
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 000000000..a95cc4f5d
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_stat_timetable.png differ
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index bff422927..e6fe6da64 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 29e8751e4..c8704a50b 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 6d683f3f8..1d43f79fd 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 0a837c46b..bfaece677 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 b4adabb98..d890fdb24 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" />
+