Compare commits

...

21 Commits

Author SHA1 Message Date
13198f2ab4 Merge branch 'release/0.20.4' into master 2020-09-13 19:00:39 +02:00
cd92f37435 Version 0.20.4 2020-09-13 19:00:32 +02:00
5d8fb376ab Expand exam sync date range to next month (#960) 2020-09-13 18:37:34 +02:00
47150364d8 Fix lifecycle of timer tasks in timetable lessons (#958)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-13 16:27:53 +02:00
792b123598 Bump coil from 1.0.0-rc1 to 1.0.0-rc2 (#961) 2020-09-13 14:07:30 +00:00
acf5c8e9ba Bump firebase-crashlytics-gradle from 2.2.1 to 2.3.0 (#964) 2020-09-13 14:04:41 +00:00
53561668fc Bump hilt_version from 2.28.3-alpha to 2.29.1-alpha (#962) 2020-09-13 14:03:09 +00:00
7cfe58d311 Bump material from 1.2.0 to 1.2.1 (#963) 2020-09-13 14:02:31 +00:00
cd51fac621 Add eduportal.koszalin.pl register (#959) 2020-09-13 13:46:45 +02:00
adde5541e2 Move timetable notifications scheduling to background thread (#954) 2020-09-11 13:02:16 +02:00
6e56d3ff06 Ignore empty semesters on refresh (#955) 2020-09-09 13:28:44 +02:00
ec761f6329 Fix bug in grade statistics (#951) 2020-09-08 20:13:17 +02:00
6363c90e37 Disable sound of upcoming lessons notification (fix) (#950) 2020-09-07 20:28:32 +02:00
c30f105be5 Fix crash on unknown attendance category type (#949) 2020-09-07 09:35:26 +02:00
9f85b2206a Merge tag '0.20.3' into develop
Version 0.20.3
2020-09-04 23:42:12 +02:00
42515fd084 Merge branch 'release/0.20.3' into master 2020-09-04 23:42:07 +02:00
9a7c04fe7b Version 0.20.3 2020-09-04 23:42:01 +02:00
debb21f5f9 Add full stacktrace to errors list in sync now (#945) 2020-09-03 21:10:39 +02:00
18b9bf42e1 Fix crash in flowWithResourceIn() (#944) 2020-09-03 20:54:28 +02:00
6ded83d132 Fix attendance item description (#943) 2020-09-03 20:52:24 +02:00
71d37a1c6c Merge tag '0.20.2' into develop
Version 0.20.2
2020-09-02 00:23:49 +02:00
25 changed files with 144 additions and 66 deletions

View File

@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.20.2 - 0.20.4
android: android:
licenses: licenses:

View File

@ -18,8 +18,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 29 targetSdkVersion 29
versionCode 66 versionCode 68
versionName "0.20.2" versionName "0.20.4"
multiDexEnabled true multiDexEnabled true
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -126,7 +126,7 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:0.20.2" implementation "io.github.wulkanowy:sdk:0.20.4"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
@ -147,7 +147,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.1" implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.2.0" implementation "com.google.android.material:material:1.2.1"
implementation "com.github.wulkanowy:material-chips-input:2.1.1" implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -176,7 +176,7 @@ dependencies {
implementation "fr.bipi.treessence:treessence:0.3.2" implementation "fr.bipi.treessence:treessence:0.3.2"
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:1.0.0-rc1" implementation "io.coil-kt:coil:1.0.0-rc2"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'me.xdrop:fuzzywuzzy:1.3.1'

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
data class Resource<out T>(val status: Status, val data: T?, val error: Throwable?) { data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
companion object { companion object {
fun <T> success(data: T?): Resource<T> { fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null) return Resource(Status.SUCCESS, data, null)

View File

@ -2,9 +2,9 @@ package io.github.wulkanowy.data.repositories.exam
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.utils.monday import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -18,8 +18,8 @@ class ExamRepository @Inject constructor(
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh }, shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getExams(semester, start.monday, end.sunday) }, query = { local.getExams(semester, start.startExamsDay, start.endExamsDay) },
fetch = { remote.getExams(student, semester, start.monday, end.sunday) }, fetch = { remote.getExams(student, semester, start.startExamsDay, start.endExamsDay) },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
local.deleteExams(old uniqueSubtract new) local.deleteExams(old uniqueSubtract new)
local.saveExams(new uniqueSubtract old) local.saveExams(new uniqueSubtract old)

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.isCurrent import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -41,7 +42,7 @@ class SemesterRepository @Inject constructor(
private suspend fun refreshSemesters(student: Student) { private suspend fun refreshSemesters(student: Student) {
val new = remote.getSemesters(student) val new = remote.getSemesters(student)
if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!") if (new.isEmpty()) return Timber.i("Empty semester list!")
val old = local.getSemesters(student) val old = local.getSemesters(student)
local.deleteSemesters(old.uniqueSubtract(new)) local.deleteSemesters(old.uniqueSubtract(new))

View File

@ -26,7 +26,9 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalDateTime.now import java.time.LocalDateTime.now
@ -35,7 +37,8 @@ import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor( class TimetableNotificationSchedulerHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val alarmManager: AlarmManager, private val alarmManager: AlarmManager,
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository,
private val dispatchersProvider: DispatchersProvider,
) { ) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt() private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
@ -44,7 +47,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
} }
fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) { suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
withContext(dispatchersProvider.backgroundThread) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId)) cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
@ -53,6 +57,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
} }
} }
}
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) { private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
if (now() in range) cancelNotification() if (now() in range) cancelNotification()
@ -61,9 +66,10 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
fun scheduleNotifications(lessons: List<Timetable>, student: Student) { suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId) if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
withContext(dispatchersProvider.backgroundThread) {
lessons.groupBy { it.date } lessons.groupBy { it.date }
.map { it.value.sortedBy { lesson -> lesson.start } } .map { it.value.sortedBy { lesson -> lesson.start } }
.map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } } .map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } }
@ -84,6 +90,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
} }
} }
} }
}
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {
return Intent(context, TimetableNotificationReceiver::class.java).apply { return Intent(context, TimetableNotificationReceiver::class.java).apply {

View File

@ -44,6 +44,7 @@ class SyncManager @Inject constructor(
if (SDK_INT >= O) { if (SDK_INT >= O) {
channels.forEach { it.create() } channels.forEach { it.create() }
notificationManager.deleteNotificationChannel("lesson_channel")
notificationManager.deleteNotificationChannel("new_entries_channel") notificationManager.deleteNotificationChannel("new_entries_channel")
} }

View File

@ -50,13 +50,16 @@ class SyncWorker @WorkerInject constructor(
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred") Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
else e else {
Timber.e(e)
e
}
} }
} }
val result = when { val result = when {
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> { exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(Data.Builder() Result.failure(Data.Builder()
.putString("error", exceptions.toString()) .putString("error", exceptions.map { it.stackTraceToString() }.toString())
.build() .build()
) )
} }

View File

@ -17,7 +17,7 @@ class UpcomingLessonsChannel @Inject constructor(
) : Channel { ) : Channel {
companion object { companion object {
const val CHANNEL_ID = "lesson_channel" const val CHANNEL_ID = "upcoming_lesson_channel"
} }
override fun create() { override fun create() {

View File

@ -3,8 +3,6 @@ package io.github.wulkanowy.services.sync.works
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.repositories.exam.ExamRepository import io.github.wulkanowy.data.repositories.exam.ExamRepository
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.utils.waitForResult
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject import javax.inject.Inject
@ -12,6 +10,6 @@ import javax.inject.Inject
class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work { class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work {
override suspend fun doWork(student: Student, semester: Semester) { override suspend fun doWork(student: Student, semester: Semester) {
examRepository.getExams(student, semester, now().monday, now().sunday, true).waitForResult() examRepository.getExams(student, semester, now(), now(), true).waitForResult()
} }
} }

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -63,7 +64,7 @@ open class BasePresenter<T : BaseView>(
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job { fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
jobs[individualJobTag]?.cancel() jobs[individualJobTag]?.cancel()
val job = launchIn(this@BasePresenter) val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
jobs[individualJobTag] = job jobs[individualJobTag] = job
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job") Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
return job return job

View File

@ -9,6 +9,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.description
import javax.inject.Inject import javax.inject.Inject
class AttendanceAdapter @Inject constructor() : class AttendanceAdapter @Inject constructor() :
@ -34,7 +35,7 @@ class AttendanceAdapter @Inject constructor() :
with(holder.binding) { with(holder.binding) {
attendanceItemNumber.text = item.number.toString() attendanceItemNumber.text = item.number.toString()
attendanceItemSubject.text = item.subject attendanceItemSubject.text = item.subject
attendanceItemDescription.text = item.name attendanceItemDescription.setText(item.description)
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE } attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
attendanceItemNumber.visibility = View.GONE attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.description
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@ -43,7 +44,7 @@ class AttendanceDialog : DialogFragment() {
with(binding) { with(binding) {
attendanceDialogSubject.text = attendance.subject attendanceDialogSubject.text = attendance.subject
attendanceDialogDescription.text = attendance.name attendanceDialogDescription.setText(attendance.description)
attendanceDialogDate.text = attendance.date.toFormattedString() attendanceDialogDate.text = attendance.date.toFormattedString()
attendanceDialogNumber.text = attendance.number.toString() attendanceDialogNumber.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() } attendanceDialogClose.setOnClickListener { dismiss() }

View File

@ -164,8 +164,8 @@ class GradeStatisticsPresenter @Inject constructor(
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading grade stats result: Success") Timber.i("Loading grade stats result: Success")
view?.run { view?.run {
showEmpty(it.data!!.isEmpty()) showEmpty(it.data!!.isEmpty() || it.data.first().partial.isEmpty())
showContent(it.data.isNotEmpty()) showContent(it.data.isNotEmpty() && it.data.first().partial.isNotEmpty())
showErrorView(false) showErrorView(false)
updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)

View File

@ -6,6 +6,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
@ -44,8 +45,8 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
private val timers = mutableMapOf<Int, Timer>() private val timers = mutableMapOf<Int, Timer>()
private fun resetTimers() { fun resetTimers() {
Timber.d("Timetable timers reset") Timber.d("Timetable timers (${timers.size}) reset")
with(timers) { with(timers) {
forEach { (_, timer) -> timer.cancel() } forEach { (_, timer) -> timer.cancel() }
clear() clear()
@ -69,11 +70,6 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
} }
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
resetTimers()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val lesson = items[position] val lesson = items[position]
@ -112,8 +108,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
bindNormalDescription(binding, lesson) bindNormalDescription(binding, lesson)
bindNormalColors(binding, lesson) bindNormalColors(binding, lesson)
if (lesson.isStudentPlan && showTimers) timers[position] = timer(period = 1000) { if (lesson.isStudentPlan && showTimers) {
timers[position] = timer(period = 1000) {
if (ViewCompat.isAttachedToWindow(root)) {
root.post { updateTimeLeft(binding, lesson, position) } root.post { updateTimeLeft(binding, lesson, position) }
}
}
} else { } else {
// reset item on set changed // reset item on set changed
timetableItemTimeUntil.visibility = GONE timetableItemTimeUntil.visibility = GONE

View File

@ -185,6 +185,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
} }
override fun onDestroyView() { override fun onDestroyView() {
timetableAdapter.resetTimers()
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }

View File

@ -1,6 +1,9 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory
/** /**
* [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji] * [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji]
@ -23,4 +26,15 @@ private fun calculatePercentage(presence: Double, absence: Double): Double {
return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100 return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100
} }
inline val Attendance.description
get() = when (AttendanceCategory.getCategoryByName(name)) {
AttendanceCategory.PRESENCE -> R.string.attendance_present
AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused
AttendanceCategory.ABSENCE_EXCUSED -> R.string.attendance_absence_excused
AttendanceCategory.UNEXCUSED_LATENESS -> R.string.attendance_unexcused_lateness
AttendanceCategory.EXCUSED_LATENESS -> R.string.attendance_excused_lateness
AttendanceCategory.ABSENCE_FOR_SCHOOL_REASONS -> R.string.attendance_absence_school
AttendanceCategory.EXEMPTION -> R.string.attendance_exemption
AttendanceCategory.DELETED -> R.string.attendance_deleted
else -> R.string.attendance_unknown
}

View File

@ -89,7 +89,7 @@ fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
} }
} }
} catch (e: Throwable) { } catch (e: Throwable) {
emit(Resource.error(e)) emit(Resource.error<T>(e))
} }
} }

View File

@ -13,8 +13,7 @@ import java.time.Month
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
import java.time.format.DateTimeFormatter.ofPattern import java.time.format.DateTimeFormatter.ofPattern
import java.time.format.TextStyle.FULL_STANDALONE import java.time.format.TextStyle.FULL
import java.time.format.TextStyle.*
import java.time.temporal.TemporalAdjusters.firstInMonth import java.time.temporal.TemporalAdjusters.firstInMonth
import java.time.temporal.TemporalAdjusters.next import java.time.temporal.TemporalAdjusters.next
import java.time.temporal.TemporalAdjusters.previous import java.time.temporal.TemporalAdjusters.previous
@ -78,6 +77,12 @@ inline val LocalDate.nextOrSameSchoolDay: LocalDate
} }
} }
inline val LocalDate.startExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday
inline val LocalDate.endExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday.plusWeeks(4).minusDays(1)
inline val LocalDate.previousOrSameSchoolDay: LocalDate inline val LocalDate.previousOrSameSchoolDay: LocalDate
get() { get() {
return when (dayOfWeek) { return when (dayOfWeek) {

View File

@ -1,7 +1,7 @@
Wersja 0.20.2 Wersja 0.20.4
- naprawiliśmy powiadomienie o szczęśliwym numerku - dodaliśmy obsługę koszalińskiego dziennika
- naprawiliśmy przełączanie na nowy rok szkolny - poprawiliśmy synchronizację sprawdzianów
- naprawiliśmy funkcję resetowania hasła - wyłączyliśmy dźwięk powiadomienia przy włączonej opcji pokazywania nadchodzących lekcji w powiadomieniu
- dodaliśmy dziennik e-Skarżysko - poprawiliśmy problemy ze stabilnością we frekwencji i planie lekcji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -168,6 +168,8 @@
<string name="attendance_excused_lateness">Spóźnienie usprawiedliwione</string> <string name="attendance_excused_lateness">Spóźnienie usprawiedliwione</string>
<string name="attendance_unexcused_lateness">Spóźnienie nieusprawiedliwione</string> <string name="attendance_unexcused_lateness">Spóźnienie nieusprawiedliwione</string>
<string name="attendance_present">Obecność</string> <string name="attendance_present">Obecność</string>
<string name="attendance_deleted">Usunięty</string>
<string name="attendance_unknown">Nieznany</string>
<string name="attendance_number">Numer lekcji</string> <string name="attendance_number">Numer lekcji</string>
<string name="attendance_no_items">Brak wpisów</string> <string name="attendance_no_items">Brak wpisów</string>
<plurals name="attendance_number_absences"> <plurals name="attendance_number_absences">

View File

@ -7,6 +7,7 @@
<item>Lubelski Portal Oświatowy</item> <item>Lubelski Portal Oświatowy</item>
<item>EduNet Miasta Tarnowa</item> <item>EduNet Miasta Tarnowa</item>
<item>ResMan Rzeszów</item> <item>ResMan Rzeszów</item>
<item>Platforma Edukacyjna Koszalina</item>
<item>Rawa Mazowiecka - Platforma vEdukacja</item> <item>Rawa Mazowiecka - Platforma vEdukacja</item>
<item>Zduńska Wola - e-Urząd</item> <item>Zduńska Wola - e-Urząd</item>
<item>Sieradz - Portal oświatowy</item> <item>Sieradz - Portal oświatowy</item>
@ -27,6 +28,7 @@
<item>https://edu.lublin.eu</item> <item>https://edu.lublin.eu</item>
<item>https://umt.tarnow.pl</item> <item>https://umt.tarnow.pl</item>
<item>https://resman.pl</item> <item>https://resman.pl</item>
<item>https://eduportal.koszalin.pl</item>
<item>https://vulcan.net.pl/</item> <item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item> <item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item> <item>https://vulcan.net.pl/</item>
@ -47,6 +49,7 @@
<item>lublin</item> <item>lublin</item>
<item>tarnow</item> <item>tarnow</item>
<item>rzeszow</item> <item>rzeszow</item>
<item>koszalin</item>
<item>rawamazowiecka</item> <item>rawamazowiecka</item>
<item>zdunskawola</item> <item>zdunskawola</item>
<item>sieradz</item> <item>sieradz</item>

View File

@ -170,6 +170,8 @@
<string name="attendance_excused_lateness">Excused lateness</string> <string name="attendance_excused_lateness">Excused lateness</string>
<string name="attendance_unexcused_lateness">Unexcused lateness</string> <string name="attendance_unexcused_lateness">Unexcused lateness</string>
<string name="attendance_present">Present</string> <string name="attendance_present">Present</string>
<string name="attendance_deleted">Deleted</string>
<string name="attendance_unknown">Unknown</string>
<string name="attendance_number">Number of lesson</string> <string name="attendance_number">Number of lesson</string>
<string name="attendance_no_items">No entries</string> <string name="attendance_no_items">No entries</string>
<plurals name="attendance_number_absences"> <plurals name="attendance_number_absences">

View File

@ -171,4 +171,42 @@ class TimeExtensionTest {
assertEquals(of(2019, 5, 1), of(2019, 5, 1).getLastSchoolDayIfHoliday(2018)) assertEquals(of(2019, 5, 1), of(2019, 5, 1).getLastSchoolDayIfHoliday(2018))
assertEquals(of(2018, 5, 1), of(2019, 5, 1).getLastSchoolDayIfHoliday(2017)) assertEquals(of(2018, 5, 1), of(2019, 5, 1).getLastSchoolDayIfHoliday(2017))
} }
@Test
fun getExamsCutOffDates() {
with(of(2020, 9, 13)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 14)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 15)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 16)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 17)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 18)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 19)) {
assertEquals(of(2020, 9, 21), startExamsDay)
assertEquals(of(2020, 10, 18), endExamsDay)
}
}
} }

View File

@ -1,8 +1,8 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.4.0' kotlin_version = '1.4.10'
about_libraries = '8.3.0' about_libraries = '8.3.0'
hilt_version = "2.28.3-alpha" hilt_version = "2.29.1-alpha"
} }
repositories { repositories {
mavenCentral() mavenCentral()
@ -15,7 +15,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:4.0.1'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
classpath "com.github.triplet.gradle:play-publisher:2.7.5" classpath "com.github.triplet.gradle:play-publisher:2.7.5"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"