mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2024-11-25 06:14:37 -06:00
Merge branch 'release/2.2.2'
This commit is contained in:
commit
6071b7571b
@ -20,15 +20,15 @@ apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
namespace 'io.github.wulkanowy'
|
||||
compileSdk 33
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 133
|
||||
versionName "2.2.1"
|
||||
targetSdkVersion 34
|
||||
versionCode 134
|
||||
versionName "2.2.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
@ -185,34 +185,34 @@ huaweiPublish {
|
||||
ext {
|
||||
work_manager = "2.8.1"
|
||||
android_hilt = "1.0.0"
|
||||
room = "2.5.2"
|
||||
room = "2.6.0"
|
||||
chucker = "3.5.2"
|
||||
mockk = "1.13.8"
|
||||
coroutines = "1.7.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.2.1'
|
||||
implementation 'io.github.wulkanowy:sdk:2.2.2'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.10.1"
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation "androidx.activity:activity-ktx:1.7.2"
|
||||
implementation "androidx.activity:activity-ktx:1.8.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.6.1"
|
||||
implementation "androidx.annotation:annotation:1.7.0"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.2.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
||||
implementation "androidx.viewpager2:viewpager2:1.1.0-beta02"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
implementation "com.google.android.material:material:1.9.0"
|
||||
implementation "com.google.android.material:material:1.10.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation 'com.github.lopspower:CircularImageView:4.3.0'
|
||||
@ -236,7 +236,7 @@ dependencies {
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.11.0"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||
|
||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||
@ -248,7 +248,7 @@ dependencies {
|
||||
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
||||
implementation 'org.apache.commons:commons-text:1.10.0'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.3.1')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.4.0')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
|
@ -1,16 +1,16 @@
|
||||
apply plugin: "jacoco"
|
||||
|
||||
jacoco {
|
||||
toolVersion "0.8.7"
|
||||
toolVersion "0.8.10"
|
||||
reportsDirectory.set(file("$buildDir/reports"))
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
tasks.withType(Test).configureEach {
|
||||
jacoco.includeNoLocationClasses = true
|
||||
jacoco.excludes = ['jdk.internal.*']
|
||||
}
|
||||
|
||||
task jacocoTestReport(type: JacocoReport) {
|
||||
tasks.register('jacocoTestReport', JacocoReport) {
|
||||
|
||||
group = "Reporting"
|
||||
description = "Generate Jacoco coverage reports"
|
||||
@ -33,19 +33,19 @@ task jacocoTestReport(type: JacocoReport) {
|
||||
'**/*_Factory.*']
|
||||
|
||||
classDirectories.setFrom(fileTree(
|
||||
dir: "$buildDir/intermediates/classes/debug",
|
||||
excludes: excludes
|
||||
dir: "$buildDir/intermediates/classes/debug",
|
||||
excludes: excludes
|
||||
) + fileTree(
|
||||
dir: "$buildDir/tmp/kotlin-classes/fdroidDebug",
|
||||
excludes: excludes
|
||||
dir: "$buildDir/tmp/kotlin-classes/fdroidDebug",
|
||||
excludes: excludes
|
||||
))
|
||||
|
||||
sourceDirectories.setFrom(files([
|
||||
"src/main/java",
|
||||
"src/fdroid/java"
|
||||
"src/main/java",
|
||||
"src/fdroid/java"
|
||||
]))
|
||||
executionData.setFrom(fileTree(
|
||||
dir: project.projectDir,
|
||||
includes: ["**/*.exec", "**/*.ec"]
|
||||
dir: project.projectDir,
|
||||
includes: ["**/*.exec", "**/*.ec"]
|
||||
))
|
||||
}
|
||||
|
@ -148,6 +148,10 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
binding.attendanceNavDate.text = date
|
||||
}
|
||||
|
||||
override fun showNavigation(show: Boolean) {
|
||||
binding.attendanceNavContainer.isVisible = show
|
||||
}
|
||||
|
||||
override fun clearData() {
|
||||
with(attendanceAdapter) {
|
||||
items = emptyList()
|
||||
@ -281,7 +285,9 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
|
||||
presenter.currentDate?.let {
|
||||
outState.putLong(SAVED_DATE_KEY, it.toEpochDay())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -3,10 +3,14 @@ package io.github.wulkanowy.ui.modules.attendance
|
||||
import android.annotation.SuppressLint
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.*
|
||||
@ -14,6 +18,7 @@ import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.time.DayOfWeek
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.now
|
||||
import java.time.LocalDate.ofEpochDay
|
||||
@ -28,9 +33,10 @@ class AttendancePresenter @Inject constructor(
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<AttendanceView>(errorHandler, studentRepository) {
|
||||
|
||||
private var baseDate: LocalDate = now().previousOrSameSchoolDay
|
||||
private var initialDate: LocalDate? = null
|
||||
private var isWeekendHasLessons: Boolean = false
|
||||
|
||||
lateinit var currentDate: LocalDate
|
||||
var currentDate: LocalDate? = null
|
||||
private set
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
@ -44,27 +50,34 @@ class AttendancePresenter @Inject constructor(
|
||||
view.initView()
|
||||
Timber.i("Attendance view was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
reloadView(ofEpochDay(date ?: baseDate.toEpochDay()))
|
||||
currentDate = date?.let(::ofEpochDay)
|
||||
loadData()
|
||||
if (currentDate.isHolidays) setBaseDateOnHolidays()
|
||||
}
|
||||
|
||||
fun onPreviousDay() {
|
||||
val date = if (isWeekendHasLessons) {
|
||||
currentDate?.minusDays(1)
|
||||
} else currentDate?.previousSchoolDay
|
||||
|
||||
view?.finishActionMode()
|
||||
attendanceToExcuseList.clear()
|
||||
reloadView(currentDate.previousSchoolDay)
|
||||
reloadView(date ?: return)
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onNextDay() {
|
||||
val date = if (isWeekendHasLessons) {
|
||||
currentDate?.plusDays(1)
|
||||
} else currentDate?.nextSchoolDay
|
||||
|
||||
view?.finishActionMode()
|
||||
attendanceToExcuseList.clear()
|
||||
reloadView(currentDate.nextSchoolDay)
|
||||
reloadView(date ?: return)
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onPickDate() {
|
||||
view?.showDatePickerDialog(currentDate)
|
||||
view?.showDatePickerDialog(currentDate ?: return)
|
||||
}
|
||||
|
||||
fun onDateSet(year: Int, month: Int, day: Int) {
|
||||
@ -93,10 +106,8 @@ class AttendancePresenter @Inject constructor(
|
||||
Timber.i("Attendance view is reselected")
|
||||
view?.let { view ->
|
||||
if (view.currentStackSize == 1) {
|
||||
baseDate = now().previousOrSameSchoolDay
|
||||
|
||||
if (currentDate != baseDate) {
|
||||
reloadView(baseDate)
|
||||
if (currentDate != initialDate) {
|
||||
reloadView(initialDate ?: return)
|
||||
loadData()
|
||||
} else if (!view.isViewEmpty) {
|
||||
view.resetView()
|
||||
@ -188,19 +199,6 @@ class AttendancePresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setBaseDateOnHolidays() {
|
||||
flow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
emit(semesterRepository.getCurrentSemester(student))
|
||||
}.catch {
|
||||
Timber.i("Loading semester result: An exception occurred")
|
||||
}.onEach {
|
||||
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
|
||||
currentDate = baseDate
|
||||
reloadNavigation()
|
||||
}.launch("holidays")
|
||||
}
|
||||
|
||||
private fun loadData(forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading attendance data started")
|
||||
|
||||
@ -211,11 +209,13 @@ class AttendancePresenter @Inject constructor(
|
||||
isParent = student.isParent
|
||||
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
checkInitialAndCurrentDate(student, semester)
|
||||
attendanceRepository.getAttendance(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = currentDate,
|
||||
end = currentDate,
|
||||
start = currentDate ?: now(),
|
||||
end = currentDate ?: now(),
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
@ -231,6 +231,8 @@ class AttendancePresenter @Inject constructor(
|
||||
}.sortedBy { item -> item.number }
|
||||
}
|
||||
.onResourceData {
|
||||
isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it)
|
||||
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
showProgress(false)
|
||||
@ -238,6 +240,7 @@ class AttendancePresenter @Inject constructor(
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(it.isNotEmpty())
|
||||
updateData(it)
|
||||
reloadNavigation()
|
||||
}
|
||||
}
|
||||
.onResourceIntermediate { view?.showRefresh(true) }
|
||||
@ -263,6 +266,43 @@ class AttendancePresenter @Inject constructor(
|
||||
.launch()
|
||||
}
|
||||
|
||||
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
|
||||
if (initialDate == null) {
|
||||
val lessons = attendanceRepository.getAttendance(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = now().monday,
|
||||
end = now().sunday,
|
||||
forceRefresh = false,
|
||||
).toFirstResult().dataOrNull.orEmpty()
|
||||
isWeekendHasLessons = isWeekendHasLessons(lessons)
|
||||
initialDate = getInitialDate(semester)
|
||||
}
|
||||
|
||||
if (currentDate == null) {
|
||||
currentDate = initialDate
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWeekendHasLessons(
|
||||
lessons: List<Attendance>,
|
||||
): Boolean = lessons.any {
|
||||
it.date.dayOfWeek in listOf(
|
||||
DayOfWeek.SATURDAY,
|
||||
DayOfWeek.SUNDAY,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialDate(semester: Semester): LocalDate {
|
||||
val now = now()
|
||||
|
||||
return when {
|
||||
now.isHolidays -> now.getLastSchoolDayIfHoliday(semester.schoolYear)
|
||||
isWeekendHasLessons -> now
|
||||
else -> now.previousOrSameSchoolDay
|
||||
}
|
||||
}
|
||||
|
||||
private fun excuseAbsence(reason: String?, toExcuseList: List<Attendance>) {
|
||||
resourceFlow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
@ -311,7 +351,7 @@ class AttendancePresenter @Inject constructor(
|
||||
private fun reloadView(date: LocalDate) {
|
||||
currentDate = date
|
||||
|
||||
Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}")
|
||||
Timber.i("Reload attendance view with the date ${currentDate?.toFormattedString()}")
|
||||
view?.apply {
|
||||
showProgress(true)
|
||||
enableSwipe(false)
|
||||
@ -326,10 +366,13 @@ class AttendancePresenter @Inject constructor(
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private fun reloadNavigation() {
|
||||
val currentDate = currentDate ?: return
|
||||
|
||||
view?.apply {
|
||||
showPreButton(!currentDate.minusDays(1).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(1).isHolidays)
|
||||
updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise())
|
||||
showNavigation(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ interface AttendanceView : BaseView {
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showNavigation(show: Boolean)
|
||||
|
||||
fun showPreButton(show: Boolean)
|
||||
|
||||
fun showNextButton(show: Boolean)
|
||||
|
@ -386,7 +386,7 @@ class DashboardPresenter @Inject constructor(
|
||||
private fun loadLessons(student: Student, forceRefresh: Boolean) {
|
||||
flatResourceFlow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val date = LocalDate.now().nextOrSameSchoolDay
|
||||
val date = LocalDate.now()
|
||||
|
||||
timetableRepository.getTimetable(
|
||||
student = student,
|
||||
|
@ -22,6 +22,8 @@ import io.github.wulkanowy.databinding.ItemGradeStatisticsHeaderBinding
|
||||
import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class GradeStatisticsAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
@ -269,7 +271,7 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
valueTextSize = 12f
|
||||
valueTextColor = binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
|
||||
valueFormatter = object : ValueFormatter() {
|
||||
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%"
|
||||
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}"
|
||||
}
|
||||
colors = gradePointsColors
|
||||
}
|
||||
@ -304,15 +306,20 @@ class GradeStatisticsAdapter @Inject constructor() :
|
||||
}
|
||||
xAxis.setDrawLabels(false)
|
||||
xAxis.setDrawGridLines(false)
|
||||
|
||||
val yMaxFromValues = (max(points.others, points.student)).roundToInt() + 30f
|
||||
val yMaxFromValuesWithMargin = ((yMaxFromValues / 10.0).roundToInt() * 10).toFloat()
|
||||
val yMax = yMaxFromValuesWithMargin.coerceAtLeast(100f)
|
||||
val yLabelCount = (yMax / 10).toInt() + 1
|
||||
with(axisLeft) {
|
||||
axisMinimum = 0f
|
||||
axisMaximum = 100f
|
||||
labelCount = 11
|
||||
axisMaximum = yMax
|
||||
labelCount = yLabelCount
|
||||
}
|
||||
with(axisRight) {
|
||||
axisMinimum = 0f
|
||||
axisMaximum = 100f
|
||||
labelCount = 11
|
||||
axisMaximum = yMax
|
||||
labelCount = yLabelCount
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
@ -58,7 +58,10 @@ class TeacherPresenter @Inject constructor(
|
||||
.logResourceStatus("load teachers data")
|
||||
.onResourceData {
|
||||
view?.run {
|
||||
updateData(it.filter { item -> item.name.isNotBlank() })
|
||||
updateData(it
|
||||
.filter { item -> item.name.isNotBlank() }
|
||||
.sortedBy { it.name }
|
||||
)
|
||||
showContent(it.isNotEmpty())
|
||||
showEmpty(it.isEmpty())
|
||||
showErrorView(false)
|
||||
|
@ -35,7 +35,7 @@ class AdvancedFragment : PreferenceFragmentCompat(),
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_advanced, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,8 @@ class AdvancedPresenter @Inject constructor(
|
||||
Timber.i("Settings advanced view was initialized")
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
fun onSharedPreferenceChanged(key: String?) {
|
||||
key ?: return
|
||||
Timber.i("Change settings $key")
|
||||
analytics.logEvent("setting_changed", "name" to key)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class AppearanceFragment : PreferenceFragmentCompat(),
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,8 @@ class AppearancePresenter @Inject constructor(
|
||||
Timber.i("Settings appearance view was initialized")
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
fun onSharedPreferenceChanged(key: String?) {
|
||||
key ?: return
|
||||
Timber.i("Change settings $key")
|
||||
|
||||
preferencesRepository.apply {
|
||||
|
@ -114,7 +114,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_notifications, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,8 @@ class NotificationsPresenter @Inject constructor(
|
||||
Timber.i("Settings notifications view was initialized")
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
fun onSharedPreferenceChanged(key: String?) {
|
||||
key ?: return
|
||||
Timber.i("Change settings $key")
|
||||
|
||||
preferencesRepository.apply {
|
||||
|
@ -52,7 +52,7 @@ class SyncFragment : PreferenceFragmentCompat(),
|
||||
setPreferencesFromResource(R.xml.scheme_preferences_sync, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
presenter.onSharedPreferenceChanged(key)
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,8 @@ class SyncPresenter @Inject constructor(
|
||||
setSyncDateInView()
|
||||
}
|
||||
|
||||
fun onSharedPreferenceChanged(key: String) {
|
||||
fun onSharedPreferenceChanged(key: String?) {
|
||||
key ?: return
|
||||
Timber.i("Change settings $key")
|
||||
|
||||
preferencesRepository.apply {
|
||||
@ -52,10 +53,12 @@ class SyncPresenter @Inject constructor(
|
||||
Timber.i("Setting sync now started")
|
||||
analytics.logEvent("sync_now", "status" to "started")
|
||||
}
|
||||
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
showMessage(syncSuccessString)
|
||||
analytics.logEvent("sync_now", "status" to "success")
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED -> {
|
||||
showError(
|
||||
syncFailedString,
|
||||
@ -66,6 +69,7 @@ class SyncPresenter @Inject constructor(
|
||||
)
|
||||
analytics.logEvent("sync_now", "status" to "failed")
|
||||
}
|
||||
|
||||
else -> Timber.d("Sync now state: ${workInfo?.state}")
|
||||
}
|
||||
if (workInfo?.state?.isFinished == true) {
|
||||
|
@ -9,6 +9,7 @@ import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -160,6 +161,10 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
binding.timetableRecycler.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showNavigation(show: Boolean) {
|
||||
binding.timetableNavContainer.isVisible = true
|
||||
}
|
||||
|
||||
override fun showPreButton(show: Boolean) {
|
||||
binding.timetablePreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE
|
||||
}
|
||||
@ -193,7 +198,9 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
|
||||
presenter.currentDate?.toEpochDay()?.let {
|
||||
outState.putLong(SAVED_DATE_KEY, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable
|
||||
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
|
||||
@ -15,6 +18,8 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
@ -24,15 +29,16 @@ import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.isJustFinished
|
||||
import io.github.wulkanowy.utils.isShowTimeUntil
|
||||
import io.github.wulkanowy.utils.left
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.nextSchoolDay
|
||||
import io.github.wulkanowy.utils.previousSchoolDay
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import io.github.wulkanowy.utils.until
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import timber.log.Timber
|
||||
import java.time.DayOfWeek
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.now
|
||||
@ -51,9 +57,10 @@ class TimetablePresenter @Inject constructor(
|
||||
private val analytics: AnalyticsHelper,
|
||||
) : BasePresenter<TimetableView>(errorHandler, studentRepository) {
|
||||
|
||||
private var baseDate: LocalDate = now().nextOrSameSchoolDay
|
||||
private var initialDate: LocalDate? = null
|
||||
private var isWeekendHasLessons: Boolean = false
|
||||
|
||||
lateinit var currentDate: LocalDate
|
||||
var currentDate: LocalDate? = null
|
||||
private set
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
@ -65,23 +72,30 @@ class TimetablePresenter @Inject constructor(
|
||||
view.initView()
|
||||
Timber.i("Timetable was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
reloadView(ofEpochDay(date ?: baseDate.toEpochDay()))
|
||||
currentDate = date?.let(::ofEpochDay)
|
||||
loadData()
|
||||
if (currentDate.isHolidays) setBaseDateOnHolidays()
|
||||
}
|
||||
|
||||
fun onPreviousDay() {
|
||||
reloadView(currentDate.previousSchoolDay)
|
||||
val date = if (isWeekendHasLessons) {
|
||||
currentDate?.minusDays(1)
|
||||
} else currentDate?.previousSchoolDay
|
||||
|
||||
reloadView(date ?: return)
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onNextDay() {
|
||||
reloadView(currentDate.nextSchoolDay)
|
||||
val date = if (isWeekendHasLessons) {
|
||||
currentDate?.plusDays(1)
|
||||
} else currentDate?.nextSchoolDay
|
||||
|
||||
reloadView(date ?: return)
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onPickDate() {
|
||||
view?.showDatePickerDialog(currentDate)
|
||||
view?.showDatePickerDialog(currentDate ?: return)
|
||||
}
|
||||
|
||||
fun onDateSet(year: Int, month: Int, day: Int) {
|
||||
@ -110,10 +124,8 @@ class TimetablePresenter @Inject constructor(
|
||||
Timber.i("Timetable view is reselected")
|
||||
view?.let { view ->
|
||||
if (view.currentStackSize == 1) {
|
||||
baseDate = now().nextOrSameSchoolDay
|
||||
|
||||
if (currentDate != baseDate) {
|
||||
reloadView(baseDate)
|
||||
if (currentDate != initialDate) {
|
||||
reloadView(initialDate ?: return)
|
||||
loadData()
|
||||
} else if (!view.isViewEmpty) {
|
||||
view.resetView()
|
||||
@ -134,34 +146,25 @@ class TimetablePresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setBaseDateOnHolidays() {
|
||||
flow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
emit(semesterRepository.getCurrentSemester(student))
|
||||
}.catch {
|
||||
Timber.i("Loading semester result: An exception occurred")
|
||||
}.onEach {
|
||||
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
|
||||
currentDate = baseDate
|
||||
reloadNavigation()
|
||||
}.launch("holidays")
|
||||
}
|
||||
|
||||
private fun loadData(forceRefresh: Boolean = false) {
|
||||
flatResourceFlow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
checkInitialAndCurrentDate(student, semester)
|
||||
timetableRepository.getTimetable(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = currentDate,
|
||||
end = currentDate,
|
||||
start = currentDate ?: now(),
|
||||
end = currentDate ?: now(),
|
||||
forceRefresh = forceRefresh,
|
||||
timetableType = TimetableRepository.TimetableType.NORMAL
|
||||
)
|
||||
}
|
||||
.logResourceStatus("load timetable data")
|
||||
.onResourceData {
|
||||
isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it.lessons)
|
||||
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
showProgress(false)
|
||||
@ -169,7 +172,8 @@ class TimetablePresenter @Inject constructor(
|
||||
showContent(it.lessons.isNotEmpty())
|
||||
showEmpty(it.lessons.isEmpty())
|
||||
updateData(it.lessons)
|
||||
setDayHeaderMessage(it.headers.singleOrNull { header -> header.date == currentDate }?.content)
|
||||
setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content)
|
||||
reloadNavigation()
|
||||
}
|
||||
}
|
||||
.onResourceIntermediate { view?.showRefresh(true) }
|
||||
@ -191,6 +195,44 @@ class TimetablePresenter @Inject constructor(
|
||||
.launch()
|
||||
}
|
||||
|
||||
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
|
||||
if (initialDate == null) {
|
||||
val lessons = timetableRepository.getTimetable(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = now().monday,
|
||||
end = now().sunday,
|
||||
forceRefresh = false,
|
||||
timetableType = TimetableRepository.TimetableType.NORMAL
|
||||
).toFirstResult().dataOrNull?.lessons.orEmpty()
|
||||
isWeekendHasLessons = isWeekendHasLessons(lessons)
|
||||
initialDate = getInitialDate(semester)
|
||||
}
|
||||
|
||||
if (currentDate == null) {
|
||||
currentDate = initialDate
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWeekendHasLessons(
|
||||
lessons: List<Timetable>,
|
||||
): Boolean = lessons.any {
|
||||
it.date.dayOfWeek in listOf(
|
||||
DayOfWeek.SATURDAY,
|
||||
DayOfWeek.SUNDAY,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialDate(semester: Semester): LocalDate {
|
||||
val now = now()
|
||||
|
||||
return when {
|
||||
now.isHolidays -> now.getLastSchoolDayIfHoliday(semester.schoolYear)
|
||||
isWeekendHasLessons -> now
|
||||
else -> now.nextOrSameSchoolDay
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateData(lessons: List<Timetable>) {
|
||||
tickTimer?.cancel()
|
||||
|
||||
@ -285,7 +327,7 @@ class TimetablePresenter @Inject constructor(
|
||||
private fun reloadView(date: LocalDate) {
|
||||
currentDate = date
|
||||
|
||||
Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}")
|
||||
Timber.i("Reload timetable view with the date ${currentDate?.toFormattedString()}")
|
||||
view?.apply {
|
||||
showProgress(true)
|
||||
enableSwipe(false)
|
||||
@ -298,10 +340,13 @@ class TimetablePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun reloadNavigation() {
|
||||
val currentDate = currentDate ?: return
|
||||
|
||||
view?.apply {
|
||||
showPreButton(!currentDate.minusDays(1).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(1).isHolidays)
|
||||
updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise())
|
||||
showNavigation(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,8 @@ interface TimetableView : BaseView {
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showNavigation(show: Boolean)
|
||||
|
||||
fun showPreButton(show: Boolean)
|
||||
|
||||
fun showNextButton(show: Boolean)
|
||||
|
@ -1,6 +1,7 @@
|
||||
Wersja 2.2.1
|
||||
Wersja 2.2.2
|
||||
|
||||
– dokonaliśmy kilka poprawek na ekranie logowania
|
||||
– naprawiliśmy przypadek z błędnym wyświetlaniem starej klasy ucznia po zalogowaniu się na konto z nowej klasy
|
||||
— dodaliśmy możliwość łatwego wejścia w sobotę i niedziele w planie lekcji przy użyciu strzałek
|
||||
— poprawiliśmy wsparcie dla statystyk ocen z systemem punktowym
|
||||
— poprawiliśmy sortowanie nauczycieli w widoku Szkoła i nauczyciele
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
@ -128,7 +128,9 @@
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UnusedAttribute">
|
||||
android:visibility="gone"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/attendancePreviousButton"
|
||||
|
@ -128,7 +128,9 @@
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UnusedAttribute">
|
||||
android:visibility="gone"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/timetablePreviousButton"
|
||||
|
@ -1,8 +1,8 @@
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.9.10'
|
||||
about_libraries = '10.9.0'
|
||||
hilt_version = "2.48"
|
||||
about_libraries = '10.9.1'
|
||||
hilt_version = '2.48.1'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@ -21,7 +21,7 @@ buildscript {
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
||||
classpath "com.github.triplet.gradle:play-publisher:3.8.4"
|
||||
classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0"
|
||||
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.0.3356"
|
||||
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373"
|
||||
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
|
||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
|
||||
}
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
5
gradlew
vendored
5
gradlew
vendored
@ -130,10 +130,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
|
@ -1,4 +1 @@
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
|
||||
}
|
||||
include ':app'
|
||||
|
Loading…
Reference in New Issue
Block a user