Merge branch 'release/2.4.0'

This commit is contained in:
Mikołaj Pich 2024-02-09 19:45:01 +01:00
commit b99ba48d2c
60 changed files with 3501 additions and 307 deletions

2
.gitignore vendored
View File

@ -65,6 +65,8 @@ captures/
.idea/uiDesigner.xml .idea/uiDesigner.xml
.idea/runConfigurations.xml .idea/runConfigurations.xml
.idea/discord.xml .idea/discord.xml
.idea/migrations.xml
.idea/androidTestResultsUserPreferences.xml
# Keystore files # Keystore files
*.jks *.jks

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 145 versionCode 146
versionName "2.3.5" versionName "2.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -142,7 +142,9 @@ android {
packagingOptions { packagingOptions {
resources { resources {
excludes += ['META-INF/library_release.kotlin_module', excludes += ['META-INF/library_release.kotlin_module',
'META-INF/library-core_release.kotlin_module'] 'META-INF/library-core_release.kotlin_module',
'META-INF/LICENSE.md',
'META-INF/LICENSE-notice.md']
} }
} }
@ -162,7 +164,7 @@ play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'production' track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS releaseStatus = ReleaseStatus.IN_PROGRESS
userFraction = 0.15d userFraction = 0.50d
updatePriority = 1 updatePriority = 1
enabled.set(false) enabled.set(false)
} }
@ -193,7 +195,7 @@ ext {
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:sdk:2.3.7' implementation 'io.github.wulkanowy:sdk:2.4.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
@ -250,7 +252,7 @@ dependencies {
implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0' implementation 'org.apache.commons:commons-text:1.11.0'
playImplementation platform('com.google.firebase:firebase-bom:32.7.0') playImplementation platform('com.google.firebase:firebase-bom:32.7.1')
playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
@ -260,9 +262,9 @@ dependencies {
playImplementation "com.google.android.play:integrity:1.3.0" playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1' playImplementation 'com.google.android.play:review-ktx:2.0.1'
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0" playImplementation "com.google.android.ump:user-messaging-platform:2.2.0"
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"

File diff suppressed because it is too large Load Diff

View File

@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ScramblerTest { class ScramblerTest {
private val scrambler = Scrambler(ApplicationProvider.getApplicationContext())
@Test @Test
fun encryptDecryptTest() { fun encryptDecryptTest() {
assertEquals("TEST", decrypt(encrypt("TEST", assertEquals(
ApplicationProvider.getApplicationContext()))) "TEST", scrambler.decrypt(scrambler.encrypt("TEST"))
)
} }
@Test @Test
fun emptyTextEncryptTest() { fun emptyTextEncryptTest() {
assertFailsWith<ScramblerException> { assertFailsWith<ScramblerException> {
decrypt("") scrambler.decrypt("")
} }
assertFailsWith<ScramblerException> { assertFailsWith<ScramblerException> {
encrypt("", ApplicationProvider.getApplicationContext()) scrambler.encrypt("")
} }
} }
@Test @Test
@SdkSuppress(minSdkVersion = 18) @SdkSuppress(minSdkVersion = 18)
fun emptyKeyStoreTest() { fun emptyKeyStoreTest() {
val text = encrypt("test", ApplicationProvider.getApplicationContext()) val text = scrambler.encrypt("test")
val keyStore = KeyStore.getInstance("AndroidKeyStore") val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null) keyStore.load(null)
keyStore.deleteEntry("wulkanowy_password") keyStore.deleteEntry("wulkanowy_password")
assertFailsWith<ScramblerException> { assertFailsWith<ScramblerException> {
decrypt(text) scrambler.decrypt(text)
} }
} }
} }

View File

@ -44,6 +44,7 @@
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
android:resizeableActivity="true"
tools:ignore="DataExtractionRules,UnusedAttribute"> tools:ignore="DataExtractionRules,UnusedAttribute">
<activity <activity
android:name=".ui.modules.splash.SplashActivity" android:name=".ui.modules.splash.SplashActivity"

View File

@ -253,4 +253,8 @@ internal class DataModule {
@Singleton @Singleton
@Provides @Provides
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
@Singleton
@Provides
fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao
} }

View File

@ -14,6 +14,7 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
@ -44,6 +45,7 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
@ -154,7 +156,8 @@ import javax.inject.Singleton
TimetableHeader::class, TimetableHeader::class,
SchoolAnnouncement::class, SchoolAnnouncement::class,
Notification::class, Notification::class,
AdminMessage::class AdminMessage::class,
GradeDescriptive::class,
], ],
autoMigrations = [ autoMigrations = [
AutoMigration(from = 44, to = 45), AutoMigration(from = 44, to = 45),
@ -165,6 +168,7 @@ import javax.inject.Singleton
AutoMigration(from = 55, to = 56), AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57, spec = Migration57::class), AutoMigration(from = 56, to = 57, spec = Migration57::class),
AutoMigration(from = 57, to = 58, spec = Migration58::class), AutoMigration(from = 57, to = 58, spec = Migration58::class),
AutoMigration(from = 58, to = 59),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -173,7 +177,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 58 const val VERSION_SCHEMA = 59
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -298,4 +302,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val notificationDao: NotificationDao abstract val notificationDao: NotificationDao
abstract val adminMessagesDao: AdminMessageDao abstract val adminMessagesDao: AdminMessageDao
abstract val gradeDescriptiveDao: GradeDescriptiveDao
} }

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
interface GradeDescriptiveDao : BaseDao<GradeDescriptive> {
@Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId")
fun loadAll(semesterId: Int, studentId: Int): Flow<List<GradeDescriptive>>
}

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "GradesDescriptive")
data class GradeDescriptive(
@ColumnInfo(name = "semester_id")
val semesterId: Int,
@ColumnInfo(name = "student_id")
val studentId: Int,
val subject: String,
val description: String,
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
}

View File

@ -1,10 +1,12 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade
import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive
import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
fun List<SdkGrade>.mapToEntities(semester: Semester) = map { fun List<SdkGrade>.mapToEntities(semester: Semester) = map {
Grade( Grade(
@ -40,3 +42,15 @@ fun List<SdkGradeSummary>.mapToEntities(semester: Semester) = map {
average = it.average average = it.average
) )
} }
@JvmName("mapGradeDescriptiveToEntities")
fun List<SdkGradeDescriptive>.mapToEntities(semester: Semester) = map {
GradeDescriptive(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.subject,
description = it.description
)
}

View File

@ -1,15 +1,22 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
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.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.toLocalDate
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -22,14 +29,13 @@ import javax.inject.Singleton
class GradeRepository @Inject constructor( class GradeRepository @Inject constructor(
private val gradeDb: GradeDao, private val gradeDb: GradeDao,
private val gradeSummaryDb: GradeSummaryDao, private val gradeSummaryDb: GradeSummaryDao,
private val gradeDescriptiveDb: GradeDescriptiveDao,
private val sdk: Sdk, private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val cacheKey = "grade"
fun getGrades( fun getGrades(
student: Student, student: Student,
semester: Semester, semester: Semester,
@ -41,30 +47,52 @@ class GradeRepository @Inject constructor(
//When details is empty and summary is not, app will not use summary cache - edge case //When details is empty and summary is not, app will not use summary cache - edge case
it.first.isEmpty() it.first.isEmpty()
}, },
shouldFetch = { (details, summaries) -> shouldFetch = { (details, summaries, descriptive) ->
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired =
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester))
details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired
}, },
query = { query = {
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId) val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries } val descriptiveFlow =
gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive ->
Triple(details, summaries, descriptive)
}
}, },
fetch = { fetch = {
val (details, summary) = sdk.init(student) val (details, summary, descriptive) = sdk.init(student)
.switchSemester(semester) .switchSemester(semester)
.getGrades(semester.semesterId) .getGrades(semester.semesterId)
details.mapToEntities(semester) to summary.mapToEntities(semester) Triple(
details.mapToEntities(semester),
summary.mapToEntities(semester),
descriptive.mapToEntities(semester)
)
}, },
saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) -> saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) ->
refreshGradeDetails(student, oldDetails, newDetails, notify) refreshGradeDetails(student, oldDetails, newDetails, notify)
refreshGradeSummaries(oldSummary, newSummary, notify) refreshGradeSummaries(oldSummary, newSummary, notify)
refreshGradeDescriptions(oldDescriptive, newDescriptive, notify)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester))
} }
) )
private suspend fun refreshGradeDescriptions(
old: List<GradeDescriptive>,
new: List<GradeDescriptive>,
notify: Boolean
) {
gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
})
}
private suspend fun refreshGradeDetails( private suspend fun refreshGradeDetails(
student: Student, student: Student,
oldGrades: List<Grade>, oldGrades: List<Grade>,
@ -132,6 +160,10 @@ class GradeRepository @Inject constructor(
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
} }
fun getGradesDescriptiveFromDatabase(semester: Semester): Flow<List<GradeDescriptive>> {
return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
}
suspend fun updateGrade(grade: Grade) { suspend fun updateGrade(grade: Grade) {
return gradeDb.updateAll(listOf(grade)) return gradeDb.updateAll(listOf(grade))
} }
@ -143,4 +175,13 @@ class GradeRepository @Inject constructor(
suspend fun updateGradesSummary(gradesSummary: List<GradeSummary>) { suspend fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
return gradeSummaryDb.updateAll(gradesSummary) return gradeSummaryDb.updateAll(gradesSummary)
} }
suspend fun updateGradesDescriptive(gradesDescriptive: List<GradeDescriptive>) {
return gradeDescriptiveDb.updateAll(gradesDescriptive)
}
private companion object {
private const val GRADE_CACHE_KEY = "grade"
}
} }

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.domain.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.repositories.TimetableRepository
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import java.time.LocalDate
import javax.inject.Inject
class IsStudentHasLessonsOnWeekendUseCase @Inject constructor(
private val timetableRepository: TimetableRepository,
private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
) {
suspend operator fun invoke(
student: Student,
semester: Semester,
currentDate: LocalDate = LocalDate.now(),
): Boolean {
val lessons = timetableRepository.getTimetable(
student = student,
semester = semester,
start = currentDate.monday,
end = currentDate.sunday,
forceRefresh = false,
timetableType = TimetableRepository.TimetableType.NORMAL
).toFirstResult().dataOrNull?.lessons.orEmpty()
return isWeekendHasLessonsUseCase(lessons)
}
}

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.domain.timetable
import io.github.wulkanowy.data.db.entities.Timetable
import java.time.DayOfWeek
import javax.inject.Inject
class IsWeekendHasLessonsUseCase @Inject constructor() {
operator fun invoke(
lessons: List<Timetable>,
): Boolean = lessons.any {
it.date.dayOfWeek in listOf(
DayOfWeek.SATURDAY,
DayOfWeek.SUNDAY,
)
}
}

View File

@ -4,12 +4,12 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getPlural
import javax.inject.Inject import javax.inject.Inject
@ -88,4 +88,28 @@ class NewGradeNotification @Inject constructor(
appNotificationManager.sendMultipleNotifications(groupNotificationData, student) appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
} }
suspend fun notifyDescriptive(items: List<GradeDescriptive>, student: Student) {
val notificationDataList = items.map {
NotificationData(
title = context.getPlural(R.plurals.grade_new_items_descriptive, 1),
content = "${it.subject}: ${it.description}",
destination = Destination.Grade,
)
}
val groupNotificationData = GroupNotificationData(
notificationDataList = notificationDataList,
title = context.getPlural(R.plurals.grade_new_items_descriptive, items.size),
content = context.getPlural(
R.plurals.grade_notify_new_items_descriptive,
items.size,
items.size
),
destination = Destination.Grade,
type = NotificationType.NEW_GRADE_DESCRIPTIVE
)
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
}
} }

View File

@ -37,6 +37,10 @@ enum class NotificationType(
channel = NewGradesChannel.CHANNEL_ID, channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade, icon = R.drawable.ic_stat_grade,
), ),
NEW_GRADE_DESCRIPTIVE(
channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade,
),
NEW_HOMEWORK( NEW_HOMEWORK(
channel = NewHomeworkChannel.CHANNEL_ID, channel = NewHomeworkChannel.CHANNEL_ID,
icon = R.drawable.ic_more_homework, icon = R.drawable.ic_more_homework,

View File

@ -45,5 +45,15 @@ class GradeWork @Inject constructor(
grade.isFinalGradeNotified = true grade.isFinalGradeNotified = true
}) })
} }
gradeRepository.getGradesDescriptiveFromDatabase(semester).first()
.filter { !it.isNotified }
.let {
if (it.isNotEmpty()) newGradeNotification.notifyDescriptive(it, student)
gradeRepository.updateGradesDescriptive(it.onEach { grade ->
grade.isNotified = true
})
}
} }
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.graphics.Typeface
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.enums.SentExcuseStatus import io.github.wulkanowy.data.enums.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.descriptionRes import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.isExcusableOrNotExcused import io.github.wulkanowy.utils.isExcusableOrNotExcused
import javax.inject.Inject import javax.inject.Inject
@ -39,7 +41,33 @@ class AttendanceAdapter @Inject constructor() :
root.context.getString(R.string.all_no_data) root.context.getString(R.string.all_no_data)
} }
attendanceItemDescription.setText(item.descriptionRes) attendanceItemDescription.setText(item.descriptionRes)
attendanceItemAlert.isVisible = item.let { it.absence && !it.excused }
attendanceItemDescription.setTextColor(
root.context.getThemeAttrColor(
when {
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
else -> android.R.attr.textColorSecondary
}
)
)
if (item.exemption || item.excused) {
attendanceItemDescription.setTypeface(null, Typeface.BOLD)
} else {
attendanceItemDescription.setTypeface(null, Typeface.NORMAL)
}
attendanceItemAlert.isVisible =
item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) }
attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor(
when{
item.absence && !item.excused -> R.attr.colorAttendanceAbsence
item.lateness && !item.excused -> R.attr.colorAttendanceLateness
else -> android.R.attr.colorPrimary
}
))
attendanceItemNumber.visibility = View.GONE attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE
attendanceItemExcuseCheckbox.visibility = View.GONE attendanceItemExcuseCheckbox.visibility = View.GONE
@ -54,10 +82,12 @@ class AttendanceAdapter @Inject constructor() :
attendanceItemExcuseInfo.visibility = View.VISIBLE attendanceItemExcuseInfo.visibility = View.VISIBLE
attendanceItemAlert.visibility = View.INVISIBLE attendanceItemAlert.visibility = View.INVISIBLE
} }
SentExcuseStatus.DENIED -> { SentExcuseStatus.DENIED -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied) attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
attendanceItemExcuseInfo.visibility = View.VISIBLE attendanceItemExcuseInfo.visibility = View.VISIBLE
} }
else -> { else -> {
if (item.isExcusableOrNotExcused && excuseActionMode) { if (item.isExcusableOrNotExcused && excuseActionMode) {
attendanceItemNumber.visibility = View.GONE attendanceItemNumber.visibility = View.GONE

View File

@ -6,10 +6,12 @@ import android.view.View
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
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.databinding.DialogAttendanceBinding import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.descriptionRes import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@ -44,6 +46,16 @@ class AttendanceDialog : BaseDialogFragment<DialogAttendanceBinding>() {
with(binding) { with(binding) {
attendanceDialogSubjectValue.text = attendance.subject attendanceDialogSubjectValue.text = attendance.subject
attendanceDialogDescriptionValue.setText(attendance.descriptionRes) attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
attendanceDialogDescriptionValue.setTextColor(
root.context.getThemeAttrColor(
when {
attendance.absence && !attendance.excused -> R.attr.colorAttendanceAbsence
attendance.lateness && !attendance.excused -> R.attr.colorAttendanceLateness
else -> android.R.attr.textColorSecondary
}
)
)
attendanceDialogDateValue.text = attendance.date.toFormattedString() attendanceDialogDateValue.text = attendance.date.toFormattedString()
attendanceDialogNumberValue.text = attendance.number.toString() attendanceDialogNumberValue.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() } attendanceDialogClose.setOnClickListener { dismiss() }

View File

@ -24,11 +24,13 @@ import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
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
import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.sunday
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -56,6 +58,7 @@ class DashboardPresenter @Inject constructor(
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val attendanceSummaryRepository: AttendanceSummaryRepository, private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val timetableRepository: TimetableRepository, private val timetableRepository: TimetableRepository,
private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
private val homeworkRepository: HomeworkRepository, private val homeworkRepository: HomeworkRepository,
private val examRepository: ExamRepository, private val examRepository: ExamRepository,
private val conferenceRepository: ConferenceRepository, private val conferenceRepository: ConferenceRepository,
@ -435,14 +438,17 @@ class DashboardPresenter @Inject constructor(
private fun loadLessons(student: Student, forceRefresh: Boolean) { private fun loadLessons(student: Student, forceRefresh: Boolean) {
flatResourceFlow { flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay val date = when (isStudentHasLessonsOnWeekendUseCase(student, semester)) {
true -> LocalDate.now()
else -> LocalDate.now().nextOrSameSchoolDay
}
timetableRepository.getTimetable( timetableRepository.getTimetable(
student = student, student = student,
semester = semester, semester = semester,
start = date, start = date,
end = date, end = date.sunday,
forceRefresh = forceRefresh forceRefresh = forceRefresh,
) )
} }
.onEach { .onEach {

View File

@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDescriptiveItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems
@ -55,6 +56,14 @@ class NotificationDebugPresenter @Inject constructor(
NotificationDebugItem(R.string.grade_summary_final_grade) { n -> NotificationDebugItem(R.string.grade_summary_final_grade) { n ->
withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) } withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) }
}, },
NotificationDebugItem(R.string.grade_summary_descriptive) { n ->
withStudent {
newGradeNotification.notifyDescriptive(
debugGradeDescriptiveItems.take(n),
it
)
}
},
NotificationDebugItem(R.string.homework_title) { n -> NotificationDebugItem(R.string.homework_title) { n ->
withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) } withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) }
}, },

View File

@ -0,0 +1,48 @@
package io.github.wulkanowy.ui.modules.debug.notification.mock
import io.github.wulkanowy.data.db.entities.GradeDescriptive
val debugGradeDescriptiveItems = listOf(
generateGradeDescriptive(
"Matematyka",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive("Fizyka", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
generateGradeDescriptive(
"Geografia",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive(
"Sieci komputerowe",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive(
"Systemy operacyjne",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive(
"Język polski",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive(
"Język angielski",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive("Religia", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
generateGradeDescriptive(
"Język niemiecki",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
generateGradeDescriptive(
"Wychowanie fizyczne",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
),
)
private fun generateGradeDescriptive(subject: String, description: String) =
GradeDescriptive(
semesterId = 0,
studentId = 0,
subject = subject,
description = description
)

View File

@ -1,15 +1,23 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
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.errorOrNull
import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.mapData
import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier import io.github.wulkanowy.utils.changeModifier
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -62,6 +70,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
params = params, params = params,
) )
BOTH_SEMESTERS -> calculateCombinedAverage( BOTH_SEMESTERS -> calculateCombinedAverage(
student = student, student = student,
semesters = semesters, semesters = semesters,
@ -69,6 +78,7 @@ class GradeAverageProvider @Inject constructor(
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
config = params, config = params,
) )
ALL_YEAR -> calculateCombinedAverage( ALL_YEAR -> calculateCombinedAverage(
student = student, student = student,
semesters = semesters, semesters = semesters,
@ -189,18 +199,28 @@ class GradeAverageProvider @Inject constructor(
): Flow<Resource<List<GradeSubject>>> { ): Flow<Resource<List<GradeSubject>>> {
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
.mapResourceData { res -> .mapResourceData { res ->
val (details, summaries) = res val (details, summaries, descriptives) = res
val isAnyAverage = summaries.any { it.average != .0 } val isAnyAverage = summaries.any { it.average != .0 }
val allGrades = details.groupBy { it.subject } val allGrades = details.groupBy { it.subject }
val descriptiveGradesBySubject = descriptives.associateBy { it.subject }
val items = summaries.emulateEmptySummaries( val items = summaries
.createEmptySummariesByGradesIfNeeded(
student = student, student = student,
semester = semester, semester = semester,
grades = allGrades.toList(), grades = allGrades.toList(),
calcAverage = isAnyAverage, calcAverage = isAnyAverage,
params = params, params = params,
).map { summary -> )
.createEmptySummariesByDescriptiveGradesIfNeeded(
student = student,
semester = semester,
descriptives = descriptives,
)
.map { summary ->
val grades = allGrades[summary.subject].orEmpty() val grades = allGrades[summary.subject].orEmpty()
val descriptiveGrade = descriptiveGradesBySubject[summary.subject]
GradeSubject( GradeSubject(
subject = summary.subject, subject = summary.subject,
average = if (!isAnyAverage || params.forceAverageCalc) { average = if (!isAnyAverage || params.forceAverageCalc) {
@ -210,6 +230,7 @@ class GradeAverageProvider @Inject constructor(
points = summary.pointsSum, points = summary.pointsSum,
summary = summary, summary = summary,
grades = grades, grades = grades,
descriptive = descriptiveGrade,
isVulcanAverage = isAnyAverage isVulcanAverage = isAnyAverage
) )
} }
@ -218,7 +239,33 @@ class GradeAverageProvider @Inject constructor(
} }
} }
private fun List<GradeSummary>.emulateEmptySummaries( private fun List<GradeSummary>.createEmptySummariesByDescriptiveGradesIfNeeded(
student: Student,
semester: Semester,
descriptives: List<GradeDescriptive>
): List<GradeSummary> {
val summarySubjects = this.map { it.subject }
val gradeSummaryToAdd = descriptives.mapNotNull { gradeDescriptive ->
if (gradeDescriptive.subject in summarySubjects) return@mapNotNull null
GradeSummary(
studentId = student.studentId,
semesterId = semester.semesterId,
position = 0,
subject = gradeDescriptive.subject,
predictedGrade = "",
finalGrade = "",
proposedPoints = "",
finalPoints = "",
pointsSum = "",
average = .0
)
}
return this + gradeSummaryToAdd
}
private fun List<GradeSummary>.createEmptySummariesByGradesIfNeeded(
student: Student, student: Student,
semester: Semester, semester: Semester,
grades: List<Pair<String, List<Grade>>>, grades: List<Pair<String, List<Grade>>>,

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
data class GradeSubject( data class GradeSubject(
@ -8,6 +9,7 @@ data class GradeSubject(
val average: Double, val average: Double,
val points: String, val points: String,
val summary: GradeSummary, val summary: GradeSummary,
val descriptive: GradeDescriptive?,
val grades: List<Grade>, val grades: List<Grade>,
val isVulcanAverage: Boolean val isVulcanAverage: Boolean
) )

View File

@ -1,13 +1,22 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode.* import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceData
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceIntermediate
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
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
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
@ -207,20 +216,20 @@ class GradeDetailsPresenter @Inject constructor(
AVERAGE -> gradeSubjects.sortedByDescending { it.average } AVERAGE -> gradeSubjects.sortedByDescending { it.average }
} }
} }
.map { (subject, average, points, _, grades) -> .map { gradeSubject ->
val subItems = grades val subItems = gradeSubject.grades
.sortedByDescending { it.date } .sortedByDescending { it.date }
.map { GradeDetailsItem(it, ViewType.ITEM) } .map { GradeDetailsItem(it, ViewType.ITEM) }
val gradeDetailsItems = listOf( val gradeDetailsItems = listOf(
GradeDetailsItem( GradeDetailsItem(
GradeDetailsHeader( GradeDetailsHeader(
subject = subject, subject = gradeSubject.subject,
average = average, average = gradeSubject.average,
pointsSum = points, pointsSum = gradeSubject.points,
grades = subItems grades = subItems
).apply { ).apply {
newGrades = grades.filter { grade -> !grade.isRead }.size newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
}, ViewType.HEADER }, ViewType.HEADER
) )
) )

View File

@ -2,16 +2,16 @@ package io.github.wulkanowy.ui.modules.grade.summary
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
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.GradeSummary
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
import io.github.wulkanowy.utils.calcFinalAverage import io.github.wulkanowy.utils.calcFinalAverage
import io.github.wulkanowy.utils.ifNullOrBlank
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -24,7 +24,7 @@ class GradeSummaryAdapter @Inject constructor(
ITEM(2) ITEM(2)
} }
var items = emptyList<GradeSummary>() var items = emptyList<GradeSummaryItem>()
var onCalculatedHelpClickListener: () -> Unit = {} var onCalculatedHelpClickListener: () -> Unit = {}
@ -44,9 +44,11 @@ class GradeSummaryAdapter @Inject constructor(
ViewType.HEADER.id -> HeaderViewHolder( ViewType.HEADER.id -> HeaderViewHolder(
ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false) ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false)
) )
ViewType.ITEM.id -> ItemViewHolder( ViewType.ITEM.id -> ItemViewHolder(
ItemGradeSummaryBinding.inflate(inflater, parent, false) ItemGradeSummaryBinding.inflate(inflater, parent, false)
) )
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }
@ -60,19 +62,23 @@ class GradeSummaryAdapter @Inject constructor(
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) { private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
if (items.isEmpty()) return if (items.isEmpty()) return
val gradeSummaries = items
.filter { it.gradeDescriptive == null }
.map { it.gradeSummary }
val context = binding.root.context val context = binding.root.context
val finalItemsCount = items.count { isGradeValid(it.finalGrade) } val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
val calculatedItemsCount = items.count { value -> value.average != 0.0 } val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) } val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
val finalAverage = items.calcFinalAverage( val finalAverage = gradeSummaries.calcFinalAverage(
preferencesRepository.gradePlusModifier, preferencesRepository.gradePlusModifier,
preferencesRepository.gradeMinusModifier preferencesRepository.gradeMinusModifier
) )
val calculatedAverage = items.filter { value -> value.average != 0.0 } val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 }
.map { values -> values.average } .map { values -> values.average }
.reversed() // fix average precision .reversed() // fix average precision
.average() .average()
.let { if (it.isNaN()) 0.0 else it }
with(binding) { with(binding) {
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage) gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
@ -95,16 +101,28 @@ class GradeSummaryAdapter @Inject constructor(
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) { private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummaryItem) {
with(binding) { val (gradeSummary, gradeDescriptive) = item
gradeSummaryItemTitle.text = item.subject
gradeSummaryItemPoints.text = item.pointsSum
gradeSummaryItemAverage.text = formatAverage(item.average, "")
gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim()
gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim()
gradeSummaryItemPointsContainer.visibility = with(binding) {
if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE gradeSummaryItemTitle.text = gradeSummary.subject
gradeSummaryItemPoints.text = gradeSummary.pointsSum
gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
gradeSummaryItemPredicted.text =
"${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
gradeSummaryItemFinal.text =
"${gradeSummary.finalGrade} ${gradeSummary.finalPoints}".trim()
gradeSummaryItemDescriptive.text = gradeDescriptive?.description.ifNullOrBlank {
root.context.getString(R.string.all_no_data)
}
gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
gradeSummaryItemPredictedContainer.isVisible = gradeDescriptive == null
gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
} }
} }

View File

@ -5,12 +5,10 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -72,7 +70,7 @@ class GradeSummaryFragment :
} }
} }
override fun updateData(data: List<GradeSummary>) { override fun updateData(data: List<GradeSummaryItem>) {
with(gradeSummaryAdapter) { with(gradeSummaryAdapter) {
items = data items = data
notifyDataSetChanged() notifyDataSetChanged()

View File

@ -0,0 +1,9 @@
package io.github.wulkanowy.ui.modules.grade.summary
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary
data class GradeSummaryItem(
val gradeSummary: GradeSummary,
val gradeDescriptive: GradeDescriptive?
)

View File

@ -1,9 +1,16 @@
package io.github.wulkanowy.ui.modules.grade.summary package io.github.wulkanowy.ui.modules.grade.summary
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE
import io.github.wulkanowy.data.enums.GradeSortingMode import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
import io.github.wulkanowy.data.enums.GradeSortingMode.* import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.onResourceData
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceIntermediate
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -128,7 +135,7 @@ class GradeSummaryPresenter @Inject constructor(
view?.showFinalAverageHelpDialog() view?.showFinalAverageHelpDialog()
} }
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> { private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummaryItem> {
return items return items
.filter { !checkEmpty(it) } .filter { !checkEmpty(it) }
.let { gradeSubjects -> .let { gradeSubjects ->
@ -136,13 +143,23 @@ class GradeSummaryPresenter @Inject constructor(
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage -> DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
} }
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage -> ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
gradeDetailsWithAverage.subject.lowercase() gradeDetailsWithAverage.subject.lowercase()
} }
AVERAGE -> gradeSubjects.sortedByDescending { it.average } AVERAGE -> gradeSubjects.sortedByDescending { it.average }
} }
} }
.map { it.summary.copy(average = it.average) } .map {
val gradeSummary = it.summary.copy(average = it.average)
val descriptive = it.descriptive
GradeSummaryItem(
gradeSummary = gradeSummary,
gradeDescriptive = descriptive,
)
}
} }
private fun checkEmpty(gradeSummary: GradeSubject): Boolean { private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
@ -151,6 +168,7 @@ class GradeSummaryPresenter @Inject constructor(
&& summary.predictedGrade.isBlank() && summary.predictedGrade.isBlank()
&& average == .0 && average == .0
&& points.isBlank() && points.isBlank()
&& descriptive == null
} }
} }
} }

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.grade.summary package io.github.wulkanowy.ui.modules.grade.summary
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface GradeSummaryView : BaseView { interface GradeSummaryView : BaseView {
@ -13,7 +12,7 @@ interface GradeSummaryView : BaseView {
fun initView() fun initView()
fun updateData(data: List<GradeSummary>) fun updateData(data: List<GradeSummaryItem>)
fun resetView() fun resetView()

View File

@ -7,5 +7,6 @@ data class LoginData(
val password: String, val password: String,
val baseUrl: String, val baseUrl: String,
val domainSuffix: String, val domainSuffix: String,
val symbol: String?, val defaultSymbol: String,
val userEnteredSymbol: String? = null,
) : Serializable ) : Serializable

View File

@ -71,7 +71,7 @@ class LoginAdvancedPresenter @Inject constructor(
fun updateUsernameLabel() { fun updateUsernameLabel() {
view?.apply { view?.apply {
setUsernameLabel(if ("vulcan" in formHostValue || "fakelog" in formHostValue) emailLabel else nicknameLabel) setUsernameLabel(if ("vulcan" in formHostValue || "wulkanowy" in formHostValue) emailLabel else nicknameLabel)
} }
} }
@ -79,7 +79,7 @@ class LoginAdvancedPresenter @Inject constructor(
view?.apply { view?.apply {
clearPassError() clearPassError()
clearUsernameError() clearUsernameError()
if (formHostValue.contains("fakelog")) { if (formHostValue.contains("wulkanowy")) {
setDefaultCredentials( setDefaultCredentials(
"jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999" "jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999"
) )
@ -155,7 +155,7 @@ class LoginAdvancedPresenter @Inject constructor(
password = view?.formPassValue.orEmpty().trim(), password = view?.formPassValue.orEmpty().trim(),
baseUrl = view?.formHostValue.orEmpty().trim(), baseUrl = view?.formHostValue.orEmpty().trim(),
domainSuffix = view?.formDomainSuffix.orEmpty().trim(), domainSuffix = view?.formDomainSuffix.orEmpty().trim(),
symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), defaultSymbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
) )
when (it.data.symbols.size) { when (it.data.symbols.size) {
0 -> view?.navigateToSymbol(loginData) 0 -> view?.navigateToSymbol(loginData)

View File

@ -90,7 +90,7 @@ class LoginFormPresenter @Inject constructor(
clearPassError() clearPassError()
clearUsernameError() clearUsernameError()
clearHostError() clearHostError()
if (formHostValue.contains("fakelog")) { if (formHostValue.contains("wulkanowy")) {
setCredentials("jan@fakelog.cf", "jan123") setCredentials("jan@fakelog.cf", "jan123")
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") { } else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
setCredentials("", "") setCredentials("", "")
@ -148,7 +148,7 @@ class LoginFormPresenter @Inject constructor(
password = password, password = password,
baseUrl = host, baseUrl = host,
domainSuffix = domainSuffix, domainSuffix = domainSuffix,
symbol = symbol defaultSymbol = symbol
) )
} }
@ -167,7 +167,7 @@ class LoginFormPresenter @Inject constructor(
password = loginData.password, password = loginData.password,
scrapperBaseUrl = loginData.baseUrl, scrapperBaseUrl = loginData.baseUrl,
domainSuffix = loginData.domainSuffix, domainSuffix = loginData.domainSuffix,
symbol = loginData.symbol.orEmpty(), symbol = loginData.defaultSymbol,
) )
} }
.logResourceStatus("login") .logResourceStatus("login")

View File

@ -38,7 +38,7 @@ class LoginRecoverPresenter @Inject constructor(
fun onHostSelected() { fun onHostSelected() {
view?.run { view?.run {
if ("fakelog" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf") if ("wulkanowy" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf")
clearUsernameError() clearUsernameError()
updateFields() updateFields()
} }
@ -60,7 +60,7 @@ class LoginRecoverPresenter @Inject constructor(
resourceFlow { resourceFlow {
recoverRepository.getReCaptchaSiteKey( recoverRepository.getReCaptchaSiteKey(
host, host,
symbol.ifBlank { "Default" }) symbol.ifBlank { "default" })
}.onEach { }.onEach {
when (it) { when (it) {
is Resource.Loading -> view?.run { is Resource.Loading -> view?.run {
@ -103,7 +103,7 @@ class LoginRecoverPresenter @Inject constructor(
fun onReCaptchaVerified(reCaptchaResponse: String) { fun onReCaptchaVerified(reCaptchaResponse: String) {
val username = view?.recoverNameValue.orEmpty() val username = view?.recoverNameValue.orEmpty()
val host = view?.recoverHostValue.orEmpty() val host = view?.recoverHostValue.orEmpty()
val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } val symbol = view?.formHostSymbol.ifNullOrBlank { "default" }
resourceFlow { resourceFlow {
recoverRepository.sendRecoverRequest( recoverRepository.sendRecoverRequest(

View File

@ -10,13 +10,11 @@ import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject

View File

@ -111,8 +111,8 @@ class LoginStudentSelectPresenter @Inject constructor(
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() } val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() } val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) { if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol })) add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
} }
addAll(createNotEmptySymbolItems(notEmptySymbols, students)) addAll(createNotEmptySymbolItems(notEmptySymbols, students))
@ -317,7 +317,7 @@ class LoginStudentSelectPresenter @Inject constructor(
loginData = loginData, loginData = loginData,
registerUser = registerUser, registerUser = registerUser,
lastErrorMessage = lastError?.message, lastErrorMessage = lastError?.message,
enteredSymbol = loginData.symbol, enteredSymbol = loginData.userEnteredSymbol,
) )
) )
} }

View File

@ -105,7 +105,7 @@ class LoginSupportDialog : BaseDialogFragment<DialogLoginSupportBinding>() {
"${appInfo.systemManufacturer} ${appInfo.systemModel}", "${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(), appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}", "${appInfo.versionName}-${appInfo.buildFlavor}",
supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol, supportInfo.loginData.let { "${it.baseUrl}/${it.defaultSymbol}/${it.userEnteredSymbol}" },
preferencesRepository.installationId, preferencesRepository.installationId,
getLastErrorFromStudentSelectScreen(), getLastErrorFromStudentSelectScreen(),
dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() } dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() }

View File

@ -60,7 +60,7 @@ class LoginSymbolPresenter @Inject constructor(
} }
loginData = loginData.copy( loginData = loginData.copy(
symbol = view?.symbolValue?.getNormalizedSymbol(), userEnteredSymbol = view?.symbolValue?.getNormalizedSymbol(),
) )
resourceFlow { resourceFlow {
studentRepository.getUserSubjectsFromScrapper( studentRepository.getUserSubjectsFromScrapper(
@ -68,7 +68,7 @@ class LoginSymbolPresenter @Inject constructor(
password = loginData.password, password = loginData.password,
scrapperBaseUrl = loginData.baseUrl, scrapperBaseUrl = loginData.baseUrl,
domainSuffix = loginData.domainSuffix, domainSuffix = loginData.domainSuffix,
symbol = loginData.symbol.orEmpty(), symbol = loginData.userEnteredSymbol.orEmpty(),
) )
}.onEach { user -> }.onEach { user ->
registerUser = user.dataOrNull registerUser = user.dataOrNull
@ -93,7 +93,7 @@ class LoginSymbolPresenter @Inject constructor(
else -> { else -> {
val enteredSymbolDetails = user.data.symbols val enteredSymbolDetails = user.data.symbols
.firstOrNull() .firstOrNull()
?.takeIf { it.symbol == loginData.symbol } ?.takeIf { it.symbol == loginData.userEnteredSymbol }
if (enteredSymbolDetails?.error is InvalidSymbolException) { if (enteredSymbolDetails?.error is InvalidSymbolException) {
view?.run { view?.run {

View File

@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.timetable
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import io.github.wulkanowy.data.dataOrNull
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
@ -20,8 +19,8 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.domain.timetable.IsWeekendHasLessonsUseCase
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
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
@ -31,16 +30,12 @@ import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.isJustFinished import io.github.wulkanowy.utils.isJustFinished
import io.github.wulkanowy.utils.isShowTimeUntil import io.github.wulkanowy.utils.isShowTimeUntil
import io.github.wulkanowy.utils.left import io.github.wulkanowy.utils.left
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.until import io.github.wulkanowy.utils.until
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber import timber.log.Timber
import java.time.DayOfWeek
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDate.now import java.time.LocalDate.now
@ -54,6 +49,8 @@ class TimetablePresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val timetableRepository: TimetableRepository, private val timetableRepository: TimetableRepository,
private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase,
private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase,
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository, private val prefRepository: PreferencesRepository,
private val analytics: AnalyticsHelper, private val analytics: AnalyticsHelper,
@ -165,7 +162,7 @@ class TimetablePresenter @Inject constructor(
} }
.logResourceStatus("load timetable data") .logResourceStatus("load timetable data")
.onResourceData { .onResourceData {
isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it.lessons) isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessonsUseCase(it.lessons)
view?.run { view?.run {
enableSwipe(true) enableSwipe(true)
@ -199,15 +196,7 @@ class TimetablePresenter @Inject constructor(
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) { private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
if (initialDate == null) { if (initialDate == null) {
val lessons = timetableRepository.getTimetable( isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(student, semester)
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) initialDate = getInitialDate(semester)
} }
@ -216,15 +205,6 @@ class TimetablePresenter @Inject constructor(
} }
} }
private fun isWeekendHasLessons(
lessons: List<Timetable>,
): Boolean = lessons.any {
it.date.dayOfWeek in listOf(
DayOfWeek.SATURDAY,
DayOfWeek.SUNDAY,
)
}
private fun getInitialDate(semester: Semester): LocalDate { private fun getInitialDate(semester: Semester): LocalDate {
val now = now() val now = now()

View File

@ -1,6 +1,8 @@
Wersja 2.3.5 Wersja 2.4.0
— naprawiliśmy ładowanie frekwencji dla szkół używających eduOne — naprawiliśmy logowanie do aplikacji na odmianie standardowej
— naprawiliśmy wielokrotne wysyłanie powiadomień o zmianach w planie lekcji — naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start
— dodaliśmy oceny opisowe
— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -64,10 +64,12 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:background="@drawable/ic_all_divider" android:layout_height="1dp"
android:layout_height="1dp" /> android:id="@+id/gradeSummaryItemPointsDivider"
android:background="@drawable/ic_all_divider" />
<LinearLayout <LinearLayout
android:id="@+id/gradeSummaryItemPredictedContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="35dp" android:minHeight="35dp"
@ -96,10 +98,12 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:background="@drawable/ic_all_divider" android:id="@+id/gradeSummaryItemPredictedDivider"
android:layout_height="1dp" /> android:layout_height="1dp"
android:background="@drawable/ic_all_divider" />
<LinearLayout <LinearLayout
android:id="@+id/gradeSummaryItemFinalContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="35dp" android:minHeight="35dp"
@ -128,6 +132,36 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:background="@drawable/ic_all_divider" android:layout_height="1dp"
android:layout_height="1dp" /> android:id="@+id/gradeSummaryItemFinalDivider"
android:background="@drawable/ic_all_divider" />
<LinearLayout
android:id="@+id/gradeSummaryItemDescriptiveContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="35dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="7dp"
android:fontFamily="sans-serif-medium"
android:text="@string/grade_summary_descriptive"
android:textSize="14sp" />
<TextView
android:id="@+id/gradeSummaryItemDescriptive"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="7dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="7dp"
android:textSize="14sp"
tools:maxLines="4"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -117,6 +117,7 @@
<string name="grade_summary_points">Součet bodů</string> <string name="grade_summary_points">Součet bodů</string>
<string name="grade_summary_final_grade">Konečná známka</string> <string name="grade_summary_final_grade">Konečná známka</string>
<string name="grade_summary_predicted_grade">Předpokládaná známka</string> <string name="grade_summary_predicted_grade">Předpokládaná známka</string>
<string name="grade_summary_descriptive">Popisná známka</string>
<string name="grade_summary_calculated_average">Vypočítaný průměr</string> <string name="grade_summary_calculated_average">Vypočítaný průměr</string>
<string name="grade_summary_calculated_average_help_dialog_title">Jak funguje vypočítaný průměr?</string> <string name="grade_summary_calculated_average_help_dialog_title">Jak funguje vypočítaný průměr?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\n<b>Průměr známek pouze z vybraného semestru</b>:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\n<b>Průměr průměrů z obou semestrů</b>:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\n<b>Průměr známek z celého roku:</b>\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů</string> <string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\n<b>Průměr známek pouze z vybraného semestru</b>:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\n<b>Průměr průměrů z obou semestrů</b>:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\n<b>Průměr známek z celého roku:</b>\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů</string>
@ -160,6 +161,12 @@
<item quantity="many">Nové konečné známky</item> <item quantity="many">Nové konečné známky</item>
<item quantity="other">Nové konečné známky</item> <item quantity="other">Nové konečné známky</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">Nová popisná známka</item>
<item quantity="few">Nové popisné známky</item>
<item quantity="many">Nové popisné známky</item>
<item quantity="other">Nové popisné známky</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Máte %1$d novou známku</item> <item quantity="one">Máte %1$d novou známku</item>
<item quantity="few">Máte %1$d nové známky</item> <item quantity="few">Máte %1$d nové známky</item>
@ -178,6 +185,12 @@
<item quantity="many">Máte %1$d nových konečných známek</item> <item quantity="many">Máte %1$d nových konečných známek</item>
<item quantity="other">Máte %1$d nových konečných známek</item> <item quantity="other">Máte %1$d nových konečných známek</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">Máte %1$d novou popisnou známku</item>
<item quantity="few">Máte %1$d nové popisné známky</item>
<item quantity="many">Máte %1$d nových popisných známek</item>
<item quantity="other">Máte %1$d nových popisných známek</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lekce</string> <string name="timetable_lesson">Lekce</string>
<string name="timetable_room">Učebna</string> <string name="timetable_room">Učebna</string>

View File

@ -117,6 +117,7 @@
<string name="grade_summary_points">Gesamtpunkte</string> <string name="grade_summary_points">Gesamtpunkte</string>
<string name="grade_summary_final_grade">Finaler Note</string> <string name="grade_summary_final_grade">Finaler Note</string>
<string name="grade_summary_predicted_grade">Vorhergesagte Note</string> <string name="grade_summary_predicted_grade">Vorhergesagte Note</string>
<string name="grade_summary_descriptive">Descriptive grade</string>
<string name="grade_summary_calculated_average">Berechnender Durchschnitt</string> <string name="grade_summary_calculated_average">Berechnender Durchschnitt</string>
<string name="grade_summary_calculated_average_help_dialog_title">Wie funktioniert der berechnete Durchschnitt?</string> <string name="grade_summary_calculated_average_help_dialog_title">Wie funktioniert der berechnete Durchschnitt?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\n<b>Durchschnitt der Noten nur aus dem ausgewählten Semester </b>:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\n<b>Durchschnitt der Durchschnitte aus beiden Semestern</b>:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\n<b>Durchschnitt der Noten aus dem ganzen Jahr:</b>\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte</string> <string name="grade_summary_calculated_average_help_dialog_message">Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\n<b>Durchschnitt der Noten nur aus dem ausgewählten Semester </b>:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\n<b>Durchschnitt der Durchschnitte aus beiden Semestern</b>:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\n<b>Durchschnitt der Noten aus dem ganzen Jahr:</b>\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte</string>
@ -152,6 +153,10 @@
<item quantity="one">Neue Abschlussnote</item> <item quantity="one">Neue Abschlussnote</item>
<item quantity="other">Neue Abschlussnoten</item> <item quantity="other">Neue Abschlussnoten</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">New descriptive grade</item>
<item quantity="other">New descriptive grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Du hast %1$d Note bekommen</item> <item quantity="one">Du hast %1$d Note bekommen</item>
<item quantity="other">Du hast %1$d Noten bekommen</item> <item quantity="other">Du hast %1$d Noten bekommen</item>
@ -164,6 +169,10 @@
<item quantity="one">Sie haben %1$d Abschlussnote bekommen</item> <item quantity="one">Sie haben %1$d Abschlussnote bekommen</item>
<item quantity="other">Sie haben %1$d Abschlussnoten bekommen</item> <item quantity="other">Sie haben %1$d Abschlussnoten bekommen</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">You received %1$d descriptive grade</item>
<item quantity="other">You received %1$d descriptive grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lektion</string> <string name="timetable_lesson">Lektion</string>
<string name="timetable_room">Klassenzimmer</string> <string name="timetable_room">Klassenzimmer</string>

View File

@ -32,6 +32,9 @@
<item name="colorPrimaryInverse">@color/material_dynamic_primary40</item> <item name="colorPrimaryInverse">@color/material_dynamic_primary40</item>
<item name="colorTimetableCanceled">@color/timetable_canceled_dark</item> <item name="colorTimetableCanceled">@color/timetable_canceled_dark</item>
<item name="colorTimetableChange">@color/timetable_change_dark</item> <item name="colorTimetableChange">@color/timetable_change_dark</item>
<item name="colorAttendanceAbsence">@color/attendance_absence_dark</item>
<item name="colorAttendanceLateness">@color/attendance_lateness_dark</item>
<item name="colorError">@color/colorErrorLight</item> <item name="colorError">@color/colorErrorLight</item>
<item name="colorDivider">@color/colorDividerInverse</item> <item name="colorDivider">@color/colorDividerInverse</item>
<item name="colorSwipeRefresh">@color/material_dynamic_secondary20</item> <item name="colorSwipeRefresh">@color/material_dynamic_secondary20</item>

View File

@ -21,6 +21,8 @@
<item name="colorSurface">@color/colorSurfaceDark</item> <item name="colorSurface">@color/colorSurfaceDark</item>
<item name="colorTimetableCanceled">@color/timetable_canceled_dark</item> <item name="colorTimetableCanceled">@color/timetable_canceled_dark</item>
<item name="colorTimetableChange">@color/timetable_change_dark</item> <item name="colorTimetableChange">@color/timetable_change_dark</item>
<item name="colorAttendanceAbsence">@color/attendance_absence_dark</item>
<item name="colorAttendanceLateness">@color/attendance_lateness_dark</item>
<item name="colorError">@color/colorErrorLight</item> <item name="colorError">@color/colorErrorLight</item>
<item name="colorDivider">@color/colorDividerInverse</item> <item name="colorDivider">@color/colorDividerInverse</item>
<item name="colorSwipeRefresh">@color/colorSwipeRefreshDark</item> <item name="colorSwipeRefresh">@color/colorSwipeRefreshDark</item>

View File

@ -117,6 +117,7 @@
<string name="grade_summary_points">Suma punktów</string> <string name="grade_summary_points">Suma punktów</string>
<string name="grade_summary_final_grade">Ocena końcowa</string> <string name="grade_summary_final_grade">Ocena końcowa</string>
<string name="grade_summary_predicted_grade">Przewidywana ocena</string> <string name="grade_summary_predicted_grade">Przewidywana ocena</string>
<string name="grade_summary_descriptive">Ocena opisowa</string>
<string name="grade_summary_calculated_average">Obliczona średnia</string> <string name="grade_summary_calculated_average">Obliczona średnia</string>
<string name="grade_summary_calculated_average_help_dialog_title">Jak działa obliczona średnia?</string> <string name="grade_summary_calculated_average_help_dialog_title">Jak działa obliczona średnia?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\n<b>Średnia ocen tylko z wybranego semestru</b>:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\n<b>Średnia ze średnich z obu semestrów</b>:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semestrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\n<b>Średnia wszystkich ocen z całego roku:</b>\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej z zsumowanych średnich</string> <string name="grade_summary_calculated_average_help_dialog_message">Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\n<b>Średnia ocen tylko z wybranego semestru</b>:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\n<b>Średnia ze średnich z obu semestrów</b>:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semestrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\n<b>Średnia wszystkich ocen z całego roku:</b>\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej z zsumowanych średnich</string>
@ -160,6 +161,12 @@
<item quantity="many">Nowe oceny końcowe</item> <item quantity="many">Nowe oceny końcowe</item>
<item quantity="other">Nowe oceny końcowe</item> <item quantity="other">Nowe oceny końcowe</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">Nowa ocena opisowa</item>
<item quantity="few">Nowe oceny opisowe</item>
<item quantity="many">Nowe oceny opisowe</item>
<item quantity="other">Nowe oceny opisowe</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Masz %1$d nową ocenę</item> <item quantity="one">Masz %1$d nową ocenę</item>
<item quantity="few">Masz %1$d nowe oceny</item> <item quantity="few">Masz %1$d nowe oceny</item>
@ -178,6 +185,12 @@
<item quantity="many">Masz %1$d nowych końcowych ocen</item> <item quantity="many">Masz %1$d nowych końcowych ocen</item>
<item quantity="other">Masz %1$d nowych końcowych ocen</item> <item quantity="other">Masz %1$d nowych końcowych ocen</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">Masz %1$d nową ocenę opisową</item>
<item quantity="few">Masz %1$d nowe oceny opisowe</item>
<item quantity="many">Masz %1$d nowych ocen opisowych</item>
<item quantity="other">Masz %1$d nowych ocen opisowych</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lekcja</string> <string name="timetable_lesson">Lekcja</string>
<string name="timetable_room">Sala</string> <string name="timetable_room">Sala</string>

View File

@ -117,6 +117,7 @@
<string name="grade_summary_points">Сумма баллов</string> <string name="grade_summary_points">Сумма баллов</string>
<string name="grade_summary_final_grade">Итоговая оценка</string> <string name="grade_summary_final_grade">Итоговая оценка</string>
<string name="grade_summary_predicted_grade">Ожидаемая оценка</string> <string name="grade_summary_predicted_grade">Ожидаемая оценка</string>
<string name="grade_summary_descriptive">Descriptive grade</string>
<string name="grade_summary_calculated_average">Рассчитанная средняя оценка</string> <string name="grade_summary_calculated_average">Рассчитанная средняя оценка</string>
<string name="grade_summary_calculated_average_help_dialog_title">Как работает \"Рассчитанная средняя оценка\"?</string> <string name="grade_summary_calculated_average_help_dialog_title">Как работает \"Рассчитанная средняя оценка\"?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\n<b>Средняя из оценок выбранного семестра</b>:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\n<b>Средняя из средних оценок семестров</b>:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\n<b>Средняя из оценок со всего года:</b>\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел</string> <string name="grade_summary_calculated_average_help_dialog_message">Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\n<b>Средняя из оценок выбранного семестра</b>:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\n<b>Средняя из средних оценок семестров</b>:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\n<b>Средняя из оценок со всего года:</b>\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел</string>
@ -160,6 +161,12 @@
<item quantity="many">Новые итоговые оценки</item> <item quantity="many">Новые итоговые оценки</item>
<item quantity="other">Новые итоговые оценки</item> <item quantity="other">Новые итоговые оценки</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">New descriptive grade</item>
<item quantity="few">New descriptive grades</item>
<item quantity="many">New descriptive grades</item>
<item quantity="other">New descriptive grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Вы получили %1$d новую оценку</item> <item quantity="one">Вы получили %1$d новую оценку</item>
<item quantity="few">Вы получили %1$d новые оценки</item> <item quantity="few">Вы получили %1$d новые оценки</item>
@ -178,6 +185,12 @@
<item quantity="many">Вы получили %1$d новых итоговых оценок</item> <item quantity="many">Вы получили %1$d новых итоговых оценок</item>
<item quantity="other">Вы получили %1$d новых итоговые оценки</item> <item quantity="other">Вы получили %1$d новых итоговые оценки</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">You received %1$d descriptive grade</item>
<item quantity="few">You received %1$d descriptive grades</item>
<item quantity="many">You received %1$d descriptive grades</item>
<item quantity="other">You received %1$d descriptive grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Урок</string> <string name="timetable_lesson">Урок</string>
<string name="timetable_room">Аудитория</string> <string name="timetable_room">Аудитория</string>

View File

@ -117,6 +117,7 @@
<string name="grade_summary_points">Súčet bodov</string> <string name="grade_summary_points">Súčet bodov</string>
<string name="grade_summary_final_grade">Konečná známka</string> <string name="grade_summary_final_grade">Konečná známka</string>
<string name="grade_summary_predicted_grade">Predpokladaná známka</string> <string name="grade_summary_predicted_grade">Predpokladaná známka</string>
<string name="grade_summary_descriptive">Popisná známka</string>
<string name="grade_summary_calculated_average">Vypočítaný priemer</string> <string name="grade_summary_calculated_average">Vypočítaný priemer</string>
<string name="grade_summary_calculated_average_help_dialog_title">Ako funguje vypočítaný priemer?</string> <string name="grade_summary_calculated_average_help_dialog_title">Ako funguje vypočítaný priemer?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\n<b>Priemer známok iba z vybraného semestra</b>:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\n<b>Priemer priemerov z oboch semestrov</b>:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\n<b>Priemer známok z celého roka:</b>\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov</string> <string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\n<b>Priemer známok iba z vybraného semestra</b>:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\n<b>Priemer priemerov z oboch semestrov</b>:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\n<b>Priemer známok z celého roka:</b>\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov</string>
@ -160,6 +161,12 @@
<item quantity="many">Nové konečné známky</item> <item quantity="many">Nové konečné známky</item>
<item quantity="other">Nové konečné známky</item> <item quantity="other">Nové konečné známky</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">Nová popisná známka</item>
<item quantity="few">Nové popisné známky</item>
<item quantity="many">Nové popisné známky</item>
<item quantity="other">Nové popisné známky</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Máte %1$d novú známku</item> <item quantity="one">Máte %1$d novú známku</item>
<item quantity="few">Máte %1$d nové známky</item> <item quantity="few">Máte %1$d nové známky</item>
@ -178,6 +185,12 @@
<item quantity="many">Máte %1$d nových konečných známok</item> <item quantity="many">Máte %1$d nových konečných známok</item>
<item quantity="other">Máte %1$d nových konečných známok</item> <item quantity="other">Máte %1$d nových konečných známok</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">Máte %1$d novú popisnú známku</item>
<item quantity="few">Máte %1$d nové popisné známky</item>
<item quantity="many">Máte %1$d nových popisných známok</item>
<item quantity="other">Máte %1$d nových popisných známok</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Lekcia</string> <string name="timetable_lesson">Lekcia</string>
<string name="timetable_room">Učebňa</string> <string name="timetable_room">Učebňa</string>

View File

@ -97,8 +97,8 @@
<string name="main_log_in">Увійти</string> <string name="main_log_in">Увійти</string>
<string name="main_session_expired">Минув термін дії сесії</string> <string name="main_session_expired">Минув термін дії сесії</string>
<string name="main_session_relogin">Минув термін дії сесії, авторизуйтеся знову</string> <string name="main_session_relogin">Минув термін дії сесії, авторизуйтеся знову</string>
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string> <string name="main_expired_credentials_description">Пароль вашого облікового запису був змінений. Ви повинні увійти в Wulkanowy знову</string>
<string name="main_expired_credentials_title">Password changed</string> <string name="main_expired_credentials_title">Пароль змінено</string>
<string name="main_support_title">Підтримка додатку</string> <string name="main_support_title">Підтримка додатку</string>
<string name="main_support_description">Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час</string> <string name="main_support_description">Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час</string>
<string name="main_support_positive">Увімкнути рекламу</string> <string name="main_support_positive">Увімкнути рекламу</string>
@ -117,6 +117,7 @@
<string name="grade_summary_points">Всього балів</string> <string name="grade_summary_points">Всього балів</string>
<string name="grade_summary_final_grade">Підсумкова оцінка</string> <string name="grade_summary_final_grade">Підсумкова оцінка</string>
<string name="grade_summary_predicted_grade">Передбачувана оцінка</string> <string name="grade_summary_predicted_grade">Передбачувана оцінка</string>
<string name="grade_summary_descriptive">Описова оцінка</string>
<string name="grade_summary_calculated_average">Розрахована середня оцінка</string> <string name="grade_summary_calculated_average">Розрахована середня оцінка</string>
<string name="grade_summary_calculated_average_help_dialog_title">Як працює \"Розрахована середня оцінка\"?</string> <string name="grade_summary_calculated_average_help_dialog_title">Як працює \"Розрахована середня оцінка\"?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\n<b>Середні оцінки тільки за обраний семестр</b>:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\n<b>Середнє значення з обох семестрів</b>:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\n<b>Середнє значення оцінок за весь рік: </b>\n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх</string> <string name="grade_summary_calculated_average_help_dialog_message">Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\n<b>Середні оцінки тільки за обраний семестр</b>:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\n<b>Середнє значення з обох семестрів</b>:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\n<b>Середнє значення оцінок за весь рік: </b>\n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх</string>
@ -160,6 +161,12 @@
<item quantity="many">Нові підсумкові оцінки</item> <item quantity="many">Нові підсумкові оцінки</item>
<item quantity="other">Нові підсумкові оцінки</item> <item quantity="other">Нові підсумкові оцінки</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">Нова описова оцінка</item>
<item quantity="few">Нових описових оцінок</item>
<item quantity="many">Описових оцінок</item>
<item quantity="other">Нові описові оцінки</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Ви отримали %1$d нову оцінку</item> <item quantity="one">Ви отримали %1$d нову оцінку</item>
<item quantity="few">Ви отримали %1$d нові оцінки</item> <item quantity="few">Ви отримали %1$d нові оцінки</item>
@ -178,6 +185,12 @@
<item quantity="many">Ви отримали %1$d нових підсумкових оцінок</item> <item quantity="many">Ви отримали %1$d нових підсумкових оцінок</item>
<item quantity="other">Ви отримали %1$d нових підсумкових оцінок</item> <item quantity="other">Ви отримали %1$d нових підсумкових оцінок</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">Ви отримали %1$d описову оцінку</item>
<item quantity="few">Ви отримали %1$d нові описові оцінки</item>
<item quantity="many">Ви отримали %1$d нових описових оцінок</item>
<item quantity="other">Ви отримали %1$d нових описових оцінок</item>
</plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Урок</string> <string name="timetable_lesson">Урок</string>
<string name="timetable_room">Аудиторія</string> <string name="timetable_room">Аудиторія</string>

View File

@ -34,6 +34,9 @@
<item name="colorPrimaryInverse">@color/material_dynamic_primary80</item> <item name="colorPrimaryInverse">@color/material_dynamic_primary80</item>
<item name="colorTimetableCanceled">@color/timetable_canceled_light</item> <item name="colorTimetableCanceled">@color/timetable_canceled_light</item>
<item name="colorTimetableChange">@color/timetable_change_light</item> <item name="colorTimetableChange">@color/timetable_change_light</item>
<item name="colorAttendanceAbsence">@color/attendance_absence_light</item>
<item name="colorAttendanceLateness">@color/attendance_lateness_light</item>
<item name="colorError">@color/colorError</item> <item name="colorError">@color/colorError</item>
<item name="colorDivider">@color/colorDivider</item> <item name="colorDivider">@color/colorDivider</item>
<item name="colorSwipeRefresh">@color/material_dynamic_secondary90</item> <item name="colorSwipeRefresh">@color/material_dynamic_secondary90</item>

View File

@ -44,10 +44,10 @@
<item>https://vulcan.net.pl/?login</item> <item>https://vulcan.net.pl/?login</item>
<item>https://vulcan.net.pl/?login</item> <item>https://vulcan.net.pl/?login</item>
<item>https://vulcan.net.pl/?email&amp;customSuffix</item> <item>https://vulcan.net.pl/?email&amp;customSuffix</item>
<item>https://fakelog.cf/?email</item> <item>https://wulkanowy.net.pl/?email</item>
</string-array> </string-array>
<string-array name="hosts_symbols"> <string-array name="hosts_symbols">
<item>Default</item> <item>warszawa</item>
<item>opole</item> <item>opole</item>
<item>gdansk</item> <item>gdansk</item>
<item>lublin</item> <item>lublin</item>
@ -66,7 +66,7 @@
<item>gminaulanmajorat</item> <item>gminaulanmajorat</item>
<item>gminaozorkow</item> <item>gminaozorkow</item>
<item>gminalopiennikgorny</item> <item>gminalopiennikgorny</item>
<item>Default</item> <item>warszawa</item>
<item>powiatwulkanowy</item> <item>powiatwulkanowy</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -7,4 +7,6 @@
<attr name="colorMessageMedium" format="color" /> <attr name="colorMessageMedium" format="color" />
<attr name="colorMessageHigh" format="color" /> <attr name="colorMessageHigh" format="color" />
<attr name="colorOnMessageHigh" format="color" /> <attr name="colorOnMessageHigh" format="color" />
<attr name="colorAttendanceAbsence" format="color" />
<attr name="colorAttendanceLateness" format="color" />
</resources> </resources>

View File

@ -49,6 +49,12 @@
<color name="timetable_change_light">#ff8f00</color> <color name="timetable_change_light">#ff8f00</color>
<color name="timetable_change_dark">#ffd54f</color> <color name="timetable_change_dark">#ffd54f</color>
<color name="attendance_absence_light">#d32f2f</color>
<color name="attendance_absence_dark">#e57373</color>
<color name="attendance_lateness_light">#cd2a01</color>
<color name="attendance_lateness_dark">#f05d0e</color>
<color name="colorDivider">#1f000000</color> <color name="colorDivider">#1f000000</color>
<color name="colorDividerInverse">#1fffffff</color> <color name="colorDividerInverse">#1fffffff</color>

View File

@ -130,6 +130,7 @@
<string name="grade_summary_points">Total points</string> <string name="grade_summary_points">Total points</string>
<string name="grade_summary_final_grade">Final grade</string> <string name="grade_summary_final_grade">Final grade</string>
<string name="grade_summary_predicted_grade">Predicted grade</string> <string name="grade_summary_predicted_grade">Predicted grade</string>
<string name="grade_summary_descriptive">Descriptive grade</string>
<string name="grade_summary_calculated_average">Calculated average</string> <string name="grade_summary_calculated_average">Calculated average</string>
<string name="grade_summary_calculated_average_help_dialog_title">How does Calculated Average work?</string> <string name="grade_summary_calculated_average_help_dialog_title">How does Calculated Average work?</string>
<string name="grade_summary_calculated_average_help_dialog_message">The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\n<b>Average of grades only from selected semester</b>:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\n<b>Average of averages from both semesters</b>:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\n<b>Average of grades from the whole year:</b>\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages</string> <string name="grade_summary_calculated_average_help_dialog_message">The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\n<b>Average of grades only from selected semester</b>:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\n<b>Average of averages from both semesters</b>:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\n<b>Average of grades from the whole year:</b>\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages</string>
@ -165,6 +166,10 @@
<item quantity="one">New final grade</item> <item quantity="one">New final grade</item>
<item quantity="other">New final grades</item> <item quantity="other">New final grades</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">New descriptive grade</item>
<item quantity="other">New descriptive grades</item>
</plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">You received %1$d grade</item> <item quantity="one">You received %1$d grade</item>
<item quantity="other">You received %1$d grades</item> <item quantity="other">You received %1$d grades</item>
@ -177,6 +182,10 @@
<item quantity="one">You received %1$d final grade</item> <item quantity="one">You received %1$d final grade</item>
<item quantity="other">You received %1$d final grades</item> <item quantity="other">You received %1$d final grades</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">You received %1$d descriptive grade</item>
<item quantity="other">You received %1$d descriptive grades</item>
</plurals>
<!--Timetable--> <!--Timetable-->

View File

@ -19,6 +19,8 @@
<item name="colorSurface">@color/colorSurface</item> <item name="colorSurface">@color/colorSurface</item>
<item name="colorTimetableCanceled">@color/timetable_canceled_light</item> <item name="colorTimetableCanceled">@color/timetable_canceled_light</item>
<item name="colorTimetableChange">@color/timetable_change_light</item> <item name="colorTimetableChange">@color/timetable_change_light</item>
<item name="colorAttendanceAbsence">@color/attendance_absence_light</item>
<item name="colorAttendanceLateness">@color/attendance_lateness_light</item>
<item name="colorError">@color/colorError</item> <item name="colorError">@color/colorError</item>
<item name="colorDivider">@color/colorDivider</item> <item name="colorDivider">@color/colorDivider</item>
<item name="colorSwipeRefresh">@color/colorSwipeRefresh</item> <item name="colorSwipeRefresh">@color/colorSwipeRefresh</item>

View File

@ -42,6 +42,7 @@ fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalD
diaryName = "$semesterId", diaryName = "$semesterId",
schoolYear = 1970, schoolYear = 1970,
classId = 0, classId = 0,
className = "Ti",
semesterNumber = semesterName, semesterNumber = semesterName,
unitId = 1, unitId = 1,
start = start, start = start,

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
@ -42,6 +43,9 @@ class GradeRepositoryTest {
@MockK @MockK
private lateinit var gradeSummaryDb: GradeSummaryDao private lateinit var gradeSummaryDb: GradeSummaryDao
@MockK
private lateinit var gradeDescriptiveDb: GradeDescriptiveDao
@MockK(relaxUnitFun = true) @MockK(relaxUnitFun = true)
private lateinit var refreshHelper: AutoRefreshHelper private lateinit var refreshHelper: AutoRefreshHelper
@ -56,7 +60,8 @@ class GradeRepositoryTest {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false every { refreshHelper.shouldBeRefreshed(any()) } returns false
gradeRepository = GradeRepository(gradeDb, gradeSummaryDb, sdk, refreshHelper) gradeRepository =
GradeRepository(gradeDb, gradeSummaryDb, gradeDescriptiveDb, sdk, refreshHelper)
coEvery { gradeDb.deleteAll(any()) } just Runs coEvery { gradeDb.deleteAll(any()) } just Runs
coEvery { gradeDb.insertAll(any()) } returns listOf() coEvery { gradeDb.insertAll(any()) } returns listOf()
@ -68,6 +73,13 @@ class GradeRepositoryTest {
) )
coEvery { gradeSummaryDb.deleteAll(any()) } just Runs coEvery { gradeSummaryDb.deleteAll(any()) } just Runs
coEvery { gradeSummaryDb.insertAll(any()) } returns listOf() coEvery { gradeSummaryDb.insertAll(any()) } returns listOf()
coEvery { gradeDescriptiveDb.loadAll(any(), any()) } returnsMany listOf(
flowOf(listOf()),
)
coEvery { gradeDescriptiveDb.deleteAll(any()) } just Runs
coEvery { gradeDescriptiveDb.insertAll(any()) } returns listOf()
} }
@Test @Test

View File

@ -1,12 +1,16 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.Status import io.github.wulkanowy.utils.Status
@ -158,7 +162,9 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { noWeightGrades to noWeightGradesSummary } } returns resourceFlow {
Triple(noWeightGrades, noWeightGradesSummary, emptyList())
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -186,7 +192,9 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { noWeightGrades to noWeightGradesArithmeticSummary } } returns resourceFlow {
Triple(noWeightGrades, noWeightGradesArithmeticSummary, emptyList())
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -211,8 +219,24 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
emit(Resource.Loading()) emit(Resource.Loading())
emit(Resource.Intermediate(secondGradeWithModifier to secondSummariesWithModifier)) emit(
emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) Resource.Intermediate(
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
)
)
emit(
Resource.Success(
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
)
)
} }
val items = runBlocking { val items = runBlocking {
@ -253,11 +277,27 @@ class GradeAverageProviderTest {
coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow { coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow {
emit(Resource.Loading()) emit(Resource.Loading())
delay(1000) delay(1000)
emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) emit(
Resource.Success(
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
)
)
} }
coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow { coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow {
emit(Resource.Loading()) emit(Resource.Loading())
emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) emit(
Resource.Success(
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
)
)
} }
val items = runBlocking { val items = runBlocking {
@ -296,7 +336,13 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
false false
) )
} returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } } returns resourceFlow {
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
}
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
@ -304,8 +350,10 @@ class GradeAverageProviderTest {
false false
) )
} returns resourceFlow { } returns resourceFlow {
listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf( Triple(
getSummary(semesters[2].semesterId, "Język polski", 2.5) listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)),
listOf(getSummary(semesters[2].semesterId, "Język polski", 2.5)),
emptyList()
) )
} }
@ -332,7 +380,13 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
false false
) )
} returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } } returns resourceFlow {
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
}
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
@ -340,12 +394,14 @@ class GradeAverageProviderTest {
false false
) )
} returns resourceFlow { } returns resourceFlow {
emptyList<Grade>() to listOf( Triple(
emptyList(), listOf(
getSummary( getSummary(
24, 24,
"Język polski", "Język polski",
.0 .0
) )
), emptyList()
) )
} }
@ -372,14 +428,22 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { emptyList<Grade>() to emptyList() } } returns resourceFlow {
Triple(
emptyList(),
emptyList(),
emptyList()
)
}
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { emptyList<Grade>() to emptyList() } } returns resourceFlow {
Triple(emptyList(), emptyList(), emptyList())
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -404,7 +468,13 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } } returns resourceFlow {
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -438,7 +508,13 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } } returns resourceFlow {
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -472,7 +548,13 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } } returns resourceFlow {
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -506,7 +588,13 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } } returns resourceFlow {
Triple(
secondGradeWithModifier,
secondSummariesWithModifier,
emptyList()
)
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -534,7 +622,7 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to secondSummaries } } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -564,7 +652,7 @@ class GradeAverageProviderTest {
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to secondSummaries } } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -594,7 +682,7 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to firstSummaries } } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -625,8 +713,8 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
emit(Resource.Loading()) emit(Resource.Loading())
emit(Resource.Intermediate(firstGrades to firstSummaries)) emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList())))
emit(Resource.Success(firstGrades to firstSummaries)) emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList())))
} }
val items = runBlocking { val items = runBlocking {
@ -675,9 +763,11 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
firstGrades to listOf( Triple(
firstGrades, listOf(
getSummary(22, "Matematyka", 3.0), getSummary(22, "Matematyka", 3.0),
getSummary(22, "Fizyka", 3.5) getSummary(22, "Fizyka", 3.5)
), emptyList()
) )
} }
coEvery { coEvery {
@ -687,9 +777,13 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
secondGrades to listOf( Triple(
secondGrades,
listOf(
getSummary(22, "Matematyka", 3.5), getSummary(22, "Matematyka", 3.5),
getSummary(22, "Fizyka", 4.0) getSummary(22, "Fizyka", 4.0)
),
emptyList()
) )
} }
@ -723,17 +817,21 @@ class GradeAverageProviderTest {
emit(Resource.Loading()) emit(Resource.Loading())
emit( emit(
Resource.Intermediate( Resource.Intermediate(
firstGrades to listOf( Triple(
firstGrades, listOf(
getSummary(22, "Matematyka", 3.0), getSummary(22, "Matematyka", 3.0),
getSummary(22, "Fizyka", 3.5) getSummary(22, "Fizyka", 3.5)
), emptyList()
) )
) )
) )
emit( emit(
Resource.Success( Resource.Success(
firstGrades to listOf( Triple(
firstGrades, listOf(
getSummary(22, "Matematyka", 3.0), getSummary(22, "Matematyka", 3.0),
getSummary(22, "Fizyka", 3.5) getSummary(22, "Fizyka", 3.5)
), emptyList()
) )
) )
) )
@ -742,17 +840,21 @@ class GradeAverageProviderTest {
emit(Resource.Loading()) emit(Resource.Loading())
emit( emit(
Resource.Intermediate( Resource.Intermediate(
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(22, "Matematyka", 3.5), getSummary(22, "Matematyka", 3.5),
getSummary(22, "Fizyka", 4.0) getSummary(22, "Fizyka", 4.0)
), emptyList()
) )
) )
) )
emit( emit(
Resource.Success( Resource.Success(
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(22, "Matematyka", 3.5), getSummary(22, "Matematyka", 3.5),
getSummary(22, "Fizyka", 4.0) getSummary(22, "Fizyka", 4.0)
), emptyList()
) )
) )
) )
@ -803,7 +905,7 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to firstSummaries } } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
@ -811,9 +913,11 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(22, "Matematyka", 1.1), getSummary(22, "Matematyka", 1.1),
getSummary(22, "Fizyka", 7.26) getSummary(22, "Fizyka", 7.26)
), emptyList()
) )
} }
@ -850,9 +954,11 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
firstGrades to listOf( Triple(
firstGrades, listOf(
getSummary(22, "Matematyka", .0), getSummary(22, "Matematyka", .0),
getSummary(22, "Fizyka", .0) getSummary(22, "Fizyka", .0)
), emptyList()
) )
} }
coEvery { coEvery {
@ -862,9 +968,11 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(22, "Matematyka", .0), getSummary(22, "Matematyka", .0),
getSummary(22, "Fizyka", .0) getSummary(22, "Fizyka", .0)
), emptyList()
) )
} }
@ -889,24 +997,28 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow {
emit(Resource.Loading()) emit(Resource.Loading())
emit(Resource.Intermediate(firstGrades to firstSummaries)) emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList())))
emit(Resource.Success(firstGrades to firstSummaries)) emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList())))
} }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow {
emit(Resource.Loading()) emit(Resource.Loading())
emit( emit(
Resource.Intermediate( Resource.Intermediate(
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(22, "Matematyka", 1.1), getSummary(22, "Matematyka", 1.1),
getSummary(22, "Fizyka", 7.26) getSummary(22, "Fizyka", 7.26)
), emptyList()
) )
) )
) )
emit( emit(
Resource.Success( Resource.Success(
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(22, "Matematyka", 1.1), getSummary(22, "Matematyka", 1.1),
getSummary(22, "Fizyka", 7.26) getSummary(22, "Fizyka", 7.26)
), emptyList()
) )
) )
) )
@ -958,14 +1070,14 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to emptyList() } } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) }
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to emptyList() } } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -1000,14 +1112,14 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to emptyList() } } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) }
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to emptyList() } } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -1043,8 +1155,10 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
firstGrades to listOf( Triple(
firstGrades, listOf(
getSummary(22, "Matematyka", 4.0) getSummary(22, "Matematyka", 4.0)
), emptyList()
) )
} }
coEvery { coEvery {
@ -1054,8 +1168,10 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
secondGrades to listOf( Triple(
secondGrades, listOf(
getSummary(23, "Matematyka", 3.0) getSummary(23, "Matematyka", 3.0)
), emptyList()
) )
} }
@ -1092,14 +1208,20 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to firstSummaries } } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to secondSummaries.dropLast(1) } } returns resourceFlow {
Triple(
secondGrades,
secondSummaries.dropLast(1),
emptyList()
)
}
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -1134,14 +1256,20 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } } returns resourceFlow {
Triple(
firstGrades,
firstSummaries.dropLast(1),
emptyList()
)
}
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to secondSummaries } } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -1176,14 +1304,20 @@ class GradeAverageProviderTest {
semesters[1], semesters[1],
true true
) )
} returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } } returns resourceFlow {
Triple(
firstGrades,
firstSummaries.dropLast(1),
emptyList()
)
}
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
student, student,
semesters[2], semesters[2],
true true
) )
} returns resourceFlow { secondGrades to secondSummaries } } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(
@ -1219,6 +1353,7 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0),
@ -1228,7 +1363,10 @@ class GradeAverageProviderTest {
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0) getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
emptyList()
)
} }
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
@ -1237,11 +1375,15 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0), getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
emptyList()
)
} }
val items = runBlocking { val items = runBlocking {
@ -1266,6 +1408,7 @@ class GradeAverageProviderTest {
every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR)
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0),
@ -1275,14 +1418,21 @@ class GradeAverageProviderTest {
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0) getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
emptyList()
)
} }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0), getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
emptyList()
)
} }
val items = runBlocking { val items = runBlocking {
@ -1313,6 +1463,7 @@ class GradeAverageProviderTest {
coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { semesterRepository.getSemesters(student) } returns semesters
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0),
@ -1322,14 +1473,21 @@ class GradeAverageProviderTest {
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0) getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
emptyList()
)
} }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0), getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
emptyList()
)
} }
val items = runBlocking { val items = runBlocking {
@ -1366,6 +1524,7 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 5.0, weight = 2.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0),
@ -1375,7 +1534,10 @@ class GradeAverageProviderTest {
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 4.0), getGrade(22, "Fizyka", 6.0, weight = 4.0),
getGrade(22, "Fizyka", 6.0, weight = 2.0) getGrade(22, "Fizyka", 6.0, weight = 2.0)
) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)),
emptyList()
)
} }
coEvery { coEvery {
gradeRepository.getGrades( gradeRepository.getGrades(
@ -1384,11 +1546,15 @@ class GradeAverageProviderTest {
true true
) )
} returns resourceFlow { } returns resourceFlow {
Triple(
listOf( listOf(
getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 1.0),
getGrade(23, "Fizyka", 5.0, weight = 2.0), getGrade(23, "Fizyka", 5.0, weight = 2.0),
getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0)
) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) ),
listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)),
emptyList()
)
} }
val items = runBlocking { val items = runBlocking {
@ -1413,9 +1579,9 @@ class GradeAverageProviderTest {
every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false)
coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns
resourceFlow { firstGrades to firstSummaries } resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns
resourceFlow { listOf<Grade>() to firstSummaries } resourceFlow { Triple(listOf<Grade>(), firstSummaries, emptyList()) }
val items = runBlocking { val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage( gradeAverageProvider.getGradesDetailsWithAverage(

View File

@ -58,7 +58,7 @@ class LoginStudentSelectPresenterTest {
login = "", login = "",
password = "", password = "",
baseUrl = "", baseUrl = "",
symbol = null, defaultSymbol = "warszawa",
domainSuffix = "", domainSuffix = "",
) )

View File

@ -14,7 +14,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16" classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16"
classpath 'com.android.tools.build:gradle:8.2.1' classpath 'com.android.tools.build:gradle:8.2.2'
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.4.0' classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.huawei.agconnect:agcp:1.9.1.303' classpath 'com.huawei.agconnect:agcp:1.9.1.303'