Add notification about upcoming lesson (#578)

This commit is contained in:
Mikołaj Pich 2020-05-20 15:11:01 +02:00 committed by GitHub
parent 115da64167
commit 29226dd93e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 333 additions and 8 deletions

View File

@ -8,12 +8,15 @@ import androidx.test.filters.SdkSuppress
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Semester 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.TestInternetObservingStrategy
import io.github.wulkanowy.data.repositories.getStudent import io.github.wulkanowy.data.repositories.getStudent
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.reactivex.Single import io.reactivex.Single
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@ -34,11 +37,17 @@ class TimetableRepositoryTest {
.strategy(TestInternetObservingStrategy()) .strategy(TestInternetObservingStrategy())
.build() .build()
@MockK
private lateinit var studentMock: Student
private val student = getStudent() private val student = getStudent()
@MockK @MockK
private lateinit var semesterMock: Semester private lateinit var semesterMock: Semester
@MockK
private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper
private lateinit var timetableRemote: TimetableRemote private lateinit var timetableRemote: TimetableRemote
private lateinit var timetableLocal: TimetableLocal private lateinit var timetableLocal: TimetableLocal
@ -52,10 +61,17 @@ class TimetableRepositoryTest {
timetableLocal = TimetableLocal(testDb.timetableDao) timetableLocal = TimetableLocal(testDb.timetableDao)
timetableRemote = TimetableRemote(mockSdk) 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.studentId } returns 1
every { semesterMock.diaryId } returns 2 every { semesterMock.diaryId } returns 2
every { semesterMock.schoolYear } returns 2019 every { semesterMock.schoolYear } returns 2019
every { semesterMock.semesterId } returns 1 every { semesterMock.semesterId } returns 1
every { mockSdk.switchDiary(any(), any()) } returns mockSdk every { mockSdk.switchDiary(any(), any()) } returns mockSdk
} }
@ -80,7 +96,7 @@ class TimetableRepositoryTest {
createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F") 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) .getTimetable(student, semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true)
.blockingGet() .blockingGet()
@ -126,7 +142,7 @@ class TimetableRepositoryTest {
createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) 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) .getTimetable(student, semesterMock, LocalDate.of(2019, 12, 23), LocalDate.of(2019, 12, 25), true)
.blockingGet() .blockingGet()

View File

@ -92,6 +92,8 @@
android:resource="@xml/provider_widget_lucky_number" /> android:resource="@xml/provider_widget_lucky_number" />
</receiver> </receiver>
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
<provider <provider
android:name="androidx.work.impl.WorkManagerInitializer" android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init" android:authorities="${applicationId}.workmanager-init"

View File

@ -1,9 +1,11 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
import android.app.AlarmManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.AssetManager import android.content.res.AssetManager
import android.content.res.Resources import android.content.res.Resources
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy

View File

@ -55,6 +55,10 @@ class PreferencesRepository @Inject constructor(
val isNotificationsEnable: Boolean val isNotificationsEnable: Boolean
get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable) get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable)
val isUpcomingLessonsNotificationsEnableKey = context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
val isUpcomingLessonsNotificationsEnable: Boolean
get() = getBoolean(isUpcomingLessonsNotificationsEnableKey, R.bool.pref_default_notification_upcoming_lessons_enable)
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug) val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean val isDebugNotificationEnable: Boolean
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug) get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)

View File

@ -5,6 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
@ -18,7 +19,8 @@ import javax.inject.Singleton
class TimetableRepository @Inject constructor( class TimetableRepository @Inject constructor(
private val settings: InternetObservingSettings, private val settings: InternetObservingSettings,
private val local: TimetableLocal, private val local: TimetableLocal,
private val remote: TimetableRemote private val remote: TimetableRemote,
private val schedulerHelper: TimetableNotificationSchedulerHelper
) { ) {
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Timetable>> { fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Timetable>> {
@ -31,8 +33,8 @@ class TimetableRepository @Inject constructor(
local.getTimetable(semester, monday, friday) local.getTimetable(semester, monday, friday)
.toSingle(emptyList()) .toSingle(emptyList())
.doOnSuccess { old -> .doOnSuccess { old ->
local.deleteTimetable(old.uniqueSubtract(new)) local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) })
local.saveTimetable(new.uniqueSubtract(old).map { item -> local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item ->
item.also { new -> item.also { new ->
old.singleOrNull { new.start == it.start }?.let { old -> old.singleOrNull { new.start == it.start }?.let { old ->
return@map new.copy( return@map new.copy(
@ -45,7 +47,7 @@ class TimetableRepository @Inject constructor(
} }
}.flatMap { }.flatMap {
local.getTimetable(semester, monday, friday).toSingle(emptyList()) 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) } }
} }
} }
} }

View File

@ -4,6 +4,7 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.ui.base.ErrorDialog 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.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginModule import io.github.wulkanowy.ui.modules.login.LoginModule
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
@ -48,4 +49,7 @@ internal abstract class BindingModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider
@ContributesAndroidInjector
abstract fun bindTimetableNotificationReceiver(): TimetableNotificationReceiver
} }

View File

@ -1,7 +1,9 @@
package io.github.wulkanowy.services package io.github.wulkanowy.services
import android.app.AlarmManager
import android.content.Context import android.content.Context
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.work.WorkManager import androidx.work.WorkManager
import com.squareup.inject.assisted.dagger2.AssistedModule import com.squareup.inject.assisted.dagger2.AssistedModule
import dagger.Binds 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.NewGradesChannel
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
import io.github.wulkanowy.services.sync.channels.NewNotesChannel 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.channels.PushChannel
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
import io.github.wulkanowy.services.sync.works.AttendanceWork import io.github.wulkanowy.services.sync.works.AttendanceWork
@ -46,6 +49,10 @@ abstract class ServicesModule {
@Singleton @Singleton
@Provides @Provides
fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context) fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context)
@Singleton
@Provides
fun provideAlarmManager(context: Context): AlarmManager = context.getSystemService()!!
} }
@ContributesAndroidInjector @ContributesAndroidInjector
@ -126,4 +133,8 @@ abstract class ServicesModule {
@Binds @Binds
@IntoSet @IntoSet
abstract fun providePushChannel(channel: PushChannel): Channel abstract fun providePushChannel(channel: PushChannel): Channel
@Binds
@IntoSet
abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
} }

View File

@ -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()
)
}
}

View File

@ -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<Timetable>, lesson: Timetable): LocalDateTime {
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
}
fun cancelScheduled(lessons: List<Timetable>, 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<LocalDateTime>, 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<Timetable>, 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")
}
}

View File

@ -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)
}
)
}
}

View File

@ -4,6 +4,7 @@ import androidx.work.WorkInfo
import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerCollector
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository 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.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -20,6 +21,7 @@ class SettingsPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val timetableNotificationHelper: TimetableNotificationSchedulerHelper,
private val analytics: FirebaseAnalyticsHelper, private val analytics: FirebaseAnalyticsHelper,
private val syncManager: SyncManager, private val syncManager: SyncManager,
private val chuckerCollector: ChuckerCollector, private val chuckerCollector: ChuckerCollector,
@ -36,17 +38,17 @@ class SettingsPresenter @Inject constructor(
fun onSharedPreferenceChanged(key: String) { fun onSharedPreferenceChanged(key: String) {
Timber.i("Change settings $key") Timber.i("Change settings $key")
with(preferencesRepository) { preferencesRepository.apply {
when (key) { when (key) {
serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() } serviceEnableKey -> with(syncManager) { if (isServiceEnabled) startPeriodicSyncWorker() else stopSyncWorker() }
servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true) servicesIntervalKey, servicesOnlyWifiKey -> syncManager.startPeriodicSyncWorker(true)
isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable isDebugNotificationEnableKey -> chuckerCollector.showNotification = isDebugNotificationEnable
appThemeKey -> view?.recreateView() appThemeKey -> view?.recreateView()
isUpcomingLessonsNotificationsEnableKey -> if (!isUpcomingLessonsNotificationsEnable) timetableNotificationHelper.cancelNotification()
appLanguageKey -> view?.run { appLanguageKey -> view?.run {
updateLanguage(if (appLanguage == "system") appInfo.systemLanguage else appLanguage) updateLanguage(if (appLanguage == "system") appInfo.systemLanguage else appLanguage)
recreateView() recreateView()
} }
else -> Unit
} }
} }
analytics.logEvent("setting_changed", "name" to key) analytics.logEvent("setting_changed", "name" to key)

View File

@ -4,9 +4,13 @@ import org.threeten.bp.DayOfWeek.FRIDAY
import org.threeten.bp.DayOfWeek.MONDAY import org.threeten.bp.DayOfWeek.MONDAY
import org.threeten.bp.DayOfWeek.SATURDAY import org.threeten.bp.DayOfWeek.SATURDAY
import org.threeten.bp.DayOfWeek.SUNDAY import org.threeten.bp.DayOfWeek.SUNDAY
import org.threeten.bp.Instant.ofEpochMilli
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalDateTime.ofInstant
import org.threeten.bp.Month 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.DateTimeFormatter.ofPattern
import org.threeten.bp.format.TextStyle.FULL_STANDALONE import org.threeten.bp.format.TextStyle.FULL_STANDALONE
import org.threeten.bp.temporal.TemporalAdjusters.firstInMonth 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 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 LocalDate.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format))
fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format)) fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format))

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

View File

@ -121,6 +121,9 @@
<string name="timetable_time">Godziny</string> <string name="timetable_time">Godziny</string>
<string name="timetable_changes">Zmiany</string> <string name="timetable_changes">Zmiany</string>
<string name="timetable_no_items">Brak lekcji w tym dniu</string> <string name="timetable_no_items">Brak lekcji w tym dniu</string>
<string name="timetable_now">Teraz: %s</string>
<string name="timetable_next">Za chwilę: %s</string>
<string name="timetable_later">Później: %s</string>
<!--Completed lessons--> <!--Completed lessons-->
<string name="completed_lessons_title">Lekcje zrealizowane</string> <string name="completed_lessons_title">Lekcje zrealizowane</string>
<string name="completed_lessons_button">Zobacz lekcje zrealizowane</string> <string name="completed_lessons_button">Zobacz lekcje zrealizowane</string>
@ -319,6 +322,7 @@
<string name="pref_view_app_language">Język aplikacji</string> <string name="pref_view_app_language">Język aplikacji</string>
<string name="pref_notify_header">Powiadomienia</string> <string name="pref_notify_header">Powiadomienia</string>
<string name="pref_notify_switch">Pokazuj powiadomienia</string> <string name="pref_notify_switch">Pokazuj powiadomienia</string>
<string name="pref_notify_upcoming_lessons_switch">Pokazuj powiadomienia o następnych lekcjach</string>
<string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string> <string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string>
<string name="pref_services_header">Synchronizacja</string> <string name="pref_services_header">Synchronizacja</string>
<string name="pref_services_switch">Automatyczna aktualizacja</string> <string name="pref_services_switch">Automatyczna aktualizacja</string>
@ -344,6 +348,7 @@
<string name="channel_new_message">Nowe wiadomości</string> <string name="channel_new_message">Nowe wiadomości</string>
<string name="channel_new_notes">Nowe uwagi</string> <string name="channel_new_notes">Nowe uwagi</string>
<string name="channel_push">Powiadomienia push</string> <string name="channel_push">Powiadomienia push</string>
<string name="channel_upcoming_lessons">Nadchodzące lekcje</string>
<string name="channel_debug">Debugowanie</string> <string name="channel_debug">Debugowanie</string>
<!--Colors--> <!--Colors-->
<string name="all_black">Czarny</string> <string name="all_black">Czarny</string>

View File

@ -13,6 +13,7 @@
<string name="pref_default_services_interval">60</string> <string name="pref_default_services_interval">60</string>
<bool name="pref_default_services_wifi_only">false</bool> <bool name="pref_default_services_wifi_only">false</bool>
<bool name="pref_default_notifications_enable">true</bool> <bool name="pref_default_notifications_enable">true</bool>
<bool name="pref_default_notification_upcoming_lessons_enable">false</bool>
<bool name="pref_default_notification_debug">false</bool> <bool name="pref_default_notification_debug">false</bool>
<string name="pref_default_grade_modifier_plus">0.33</string> <string name="pref_default_grade_modifier_plus">0.33</string>
<string name="pref_default_grade_modifier_minus">0.33</string> <string name="pref_default_grade_modifier_minus">0.33</string>

View File

@ -14,6 +14,7 @@
<string name="pref_key_services_wifi_only">services_disable_wifi_only</string> <string name="pref_key_services_wifi_only">services_disable_wifi_only</string>
<string name="pref_key_services_force_sync">services_force_sync</string> <string name="pref_key_services_force_sync">services_force_sync</string>
<string name="pref_key_notifications_enable">notifications_enable</string> <string name="pref_key_notifications_enable">notifications_enable</string>
<string name="pref_key_notifications_upcoming_lessons_enable">notifications_upcoming_lessons_enable</string>
<string name="pref_key_notification_debug">notification_debug</string> <string name="pref_key_notification_debug">notification_debug</string>
<string name="pref_key_grade_modifier_plus">grade_modifier_plus</string> <string name="pref_key_grade_modifier_plus">grade_modifier_plus</string>
<string name="pref_key_grade_modifier_minus">grade_modifier_minus</string> <string name="pref_key_grade_modifier_minus">grade_modifier_minus</string>

View File

@ -130,6 +130,9 @@
<string name="timetable_time">Hours</string> <string name="timetable_time">Hours</string>
<string name="timetable_changes">Changes</string> <string name="timetable_changes">Changes</string>
<string name="timetable_no_items">No lessons this day</string> <string name="timetable_no_items">No lessons this day</string>
<string name="timetable_now">Now: %s</string>
<string name="timetable_next">Next: %s</string>
<string name="timetable_later">Later: %s</string>
<!--Completed lessons--> <!--Completed lessons-->
@ -356,6 +359,7 @@
<string name="pref_notify_header">Notifications</string> <string name="pref_notify_header">Notifications</string>
<string name="pref_notify_switch">Show notifications</string> <string name="pref_notify_switch">Show notifications</string>
<string name="pref_notify_upcoming_lessons_switch">Show upcoming lesson notifications</string>
<string name="pref_notify_debug_switch">Show debug notifications</string> <string name="pref_notify_debug_switch">Show debug notifications</string>
<string name="pref_services_header">Synchronization</string> <string name="pref_services_header">Synchronization</string>
@ -386,6 +390,7 @@
<string name="channel_new_message">New messages</string> <string name="channel_new_message">New messages</string>
<string name="channel_new_notes">New notes</string> <string name="channel_new_notes">New notes</string>
<string name="channel_push">Push notifications</string> <string name="channel_push">Push notifications</string>
<string name="channel_upcoming_lessons">Upcoming lessons</string>
<string name="channel_debug">Debug</string> <string name="channel_debug">Debug</string>

View File

@ -96,6 +96,11 @@
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="@string/pref_key_notifications_enable" app:key="@string/pref_key_notifications_enable"
app:title="@string/pref_notify_switch" /> app:title="@string/pref_notify_switch" />
<SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_notification_upcoming_lessons_enable"
app:iconSpaceReserved="false"
app:key="@string/pref_key_notifications_upcoming_lessons_enable"
app:title="@string/pref_notify_upcoming_lessons_switch" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_notification_debug" app:defaultValue="@bool/pref_default_notification_debug"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"