forked from github/wulkanowy-mirror
merge: v2.8.5
This commit is contained in:
commit
4fba76d327
@ -27,8 +27,8 @@ android {
|
|||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 151
|
versionCode 157
|
||||||
versionName "2.6.1"
|
versionName "2.5.8"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
resValue "string", "app_name", "Wulkanowy"
|
resValue "string", "app_name", "Wulkanowy"
|
||||||
@ -160,8 +160,8 @@ play {
|
|||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'production'
|
track = 'production'
|
||||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||||
userFraction = 0.50d
|
userFraction = 0.99d
|
||||||
updatePriority = 3
|
updatePriority = 4
|
||||||
enabled.set(false)
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,16 +191,16 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'io.github.wulkanowy:sdk:2.5.2'
|
implementation 'io.github.wulkanowy:sdk:2.5.8'
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.13.0'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
implementation "androidx.activity:activity-ktx:1.8.2"
|
implementation "androidx.activity:activity-ktx:1.9.0"
|
||||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.6.2"
|
implementation "androidx.fragment:fragment-ktx:1.6.2"
|
||||||
implementation "androidx.annotation:annotation:1.7.1"
|
implementation "androidx.annotation:annotation:1.7.1"
|
||||||
@ -233,7 +233,7 @@ dependencies {
|
|||||||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||||
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
|
||||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||||
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||||
@ -246,9 +246,9 @@ dependencies {
|
|||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
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.12.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.4')
|
playImplementation platform('com.google.firebase:firebase-bom:32.8.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:'
|
||||||
@ -274,7 +274,7 @@ dependencies {
|
|||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.11.1'
|
testImplementation 'org.robolectric:robolectric:4.12.1'
|
||||||
testImplementation "androidx.test:runner:1.5.2"
|
testImplementation "androidx.test:runner:1.5.2"
|
||||||
testImplementation "androidx.test.ext:junit:1.1.5"
|
testImplementation "androidx.test.ext:junit:1.1.5"
|
||||||
testImplementation "androidx.test:core:1.5.0"
|
testImplementation "androidx.test:core:1.5.0"
|
||||||
|
2547
app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json
Normal file
2547
app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json
Normal file
File diff suppressed because it is too large
Load Diff
2559
app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json
Normal file
2559
app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -51,7 +51,7 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||||
tools:ignore="LockedOrientationActivity">
|
tools:ignore="DiscouragedApi,LockedOrientationActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package io.github.wulkanowy.data
|
package io.github.wulkanowy.data
|
||||||
|
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
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.StudentIsEduOne
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -14,9 +18,13 @@ import javax.inject.Singleton
|
|||||||
class WulkanowySdkFactory @Inject constructor(
|
class WulkanowySdkFactory @Inject constructor(
|
||||||
private val chuckerInterceptor: ChuckerInterceptor,
|
private val chuckerInterceptor: ChuckerInterceptor,
|
||||||
private val remoteConfig: RemoteConfigHelper,
|
private val remoteConfig: RemoteConfigHelper,
|
||||||
private val webkitCookieManagerProxy: WebkitCookieManagerProxy
|
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
||||||
|
private val studentDb: StudentDao,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val eduOneMutex = Mutex()
|
||||||
|
private val migrationFailedStudentIds = mutableSetOf<Long>()
|
||||||
|
|
||||||
private val sdk = Sdk().apply {
|
private val sdk = Sdk().apply {
|
||||||
androidVersion = android.os.Build.VERSION.RELEASE
|
androidVersion = android.os.Build.VERSION.RELEASE
|
||||||
buildTag = android.os.Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
@ -30,7 +38,12 @@ class WulkanowySdkFactory @Inject constructor(
|
|||||||
|
|
||||||
fun create() = sdk
|
fun create() = sdk
|
||||||
|
|
||||||
fun create(student: Student, semester: Semester? = null): Sdk {
|
suspend fun create(student: Student, semester: Semester? = null): Sdk {
|
||||||
|
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
|
||||||
|
return buildSdk(student, semester, overrideIsEduOne)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSdk(student: Student, semester: Semester?, isStudentEduOne: Boolean): Sdk {
|
||||||
return create().apply {
|
return create().apply {
|
||||||
email = student.email
|
email = student.email
|
||||||
password = student.password
|
password = student.password
|
||||||
@ -38,8 +51,8 @@ class WulkanowySdkFactory @Inject constructor(
|
|||||||
schoolSymbol = student.schoolSymbol
|
schoolSymbol = student.schoolSymbol
|
||||||
studentId = student.studentId
|
studentId = student.studentId
|
||||||
classId = student.classId
|
classId = student.classId
|
||||||
isEduOne = student.isEduOne
|
|
||||||
emptyCookieJarInterceptor = true
|
emptyCookieJarInterceptor = true
|
||||||
|
isEduOne = isStudentEduOne
|
||||||
|
|
||||||
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
|
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
|
||||||
mobileBaseUrl = student.mobileBaseUrl
|
mobileBaseUrl = student.mobileBaseUrl
|
||||||
@ -62,4 +75,51 @@ class WulkanowySdkFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean {
|
||||||
|
if (student.isEduOne != null) return student.isEduOne
|
||||||
|
|
||||||
|
if (student.id in migrationFailedStudentIds) {
|
||||||
|
Timber.i("Migration eduOne: skipping because of previous failure")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eduOneMutex.withLock {
|
||||||
|
if (student.id in migrationFailedStudentIds) {
|
||||||
|
Timber.i("Migration eduOne: skipping because of previous failure")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val studentFromDatabase = studentDb.loadById(student.id)
|
||||||
|
if (studentFromDatabase?.isEduOne != null) {
|
||||||
|
Timber.i("Migration eduOne: already done")
|
||||||
|
return studentFromDatabase.isEduOne
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.i("Migration eduOne: flag missing. Running migration...")
|
||||||
|
val initializedSdk = buildSdk(
|
||||||
|
student = student,
|
||||||
|
semester = null,
|
||||||
|
isStudentEduOne = false, // doesn't matter
|
||||||
|
)
|
||||||
|
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
|
||||||
|
.onFailure { Timber.e(it, "Migration eduOne: can't get current student") }
|
||||||
|
.getOrNull()
|
||||||
|
|
||||||
|
if (newCurrentStudent == null) {
|
||||||
|
Timber.i("Migration eduOne: failed, so skipping")
|
||||||
|
migrationFailedStudentIds.add(student.id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}")
|
||||||
|
|
||||||
|
val studentIsEduOne = StudentIsEduOne(
|
||||||
|
id = student.id,
|
||||||
|
isEduOne = newCurrentStudent.isEduOne
|
||||||
|
)
|
||||||
|
studentDb.update(studentIsEduOne)
|
||||||
|
return newCurrentStudent.isEduOne
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ import io.github.wulkanowy.data.db.migrations.Migration55
|
|||||||
import io.github.wulkanowy.data.db.migrations.Migration57
|
import io.github.wulkanowy.data.db.migrations.Migration57
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration58
|
import io.github.wulkanowy.data.db.migrations.Migration58
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration63
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration8
|
import io.github.wulkanowy.data.db.migrations.Migration8
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration9
|
import io.github.wulkanowy.data.db.migrations.Migration9
|
||||||
@ -175,6 +176,8 @@ import javax.inject.Singleton
|
|||||||
AutoMigration(from = 59, to = 60),
|
AutoMigration(from = 59, to = 60),
|
||||||
AutoMigration(from = 60, to = 61),
|
AutoMigration(from = 60, to = 61),
|
||||||
AutoMigration(from = 61, to = 62),
|
AutoMigration(from = 61, to = 62),
|
||||||
|
AutoMigration(from = 62, to = 63, spec = Migration63::class),
|
||||||
|
AutoMigration(from = 63, to = 64),
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
@ -183,7 +186,7 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 62
|
const val VERSION_SCHEMA = 64
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
|
@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
@Dao
|
@Dao
|
||||||
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
interface MobileDeviceDao : BaseDao<MobileDevice> {
|
||||||
|
|
||||||
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC")
|
||||||
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>>
|
fun loadAll(studentId: Int): Flow<List<MobileDevice>>
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,6 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
|
||||||
|
|
||||||
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
|
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC")
|
||||||
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>>
|
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,6 @@ interface SemesterDao : BaseDao<Semester> {
|
|||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insertSemesters(items: List<Semester>): List<Long>
|
suspend fun insertSemesters(items: List<Semester>): List<Long>
|
||||||
|
|
||||||
@Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId")
|
@Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)")
|
||||||
suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
|
suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import androidx.room.Update
|
|||||||
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.StudentIsAuthorized
|
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
|
||||||
import io.github.wulkanowy.data.db.entities.StudentName
|
import io.github.wulkanowy.data.db.entities.StudentName
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -27,6 +28,9 @@ abstract class StudentDao {
|
|||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
|
abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
|
||||||
|
|
||||||
|
@Update(entity = Student::class)
|
||||||
|
abstract suspend fun update(studentIsEduOne: StudentIsEduOne)
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||||
|
|
||||||
@ -43,11 +47,11 @@ abstract class StudentDao {
|
|||||||
abstract suspend fun loadAll(): List<Student>
|
abstract suspend fun loadAll(): List<Student>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id")
|
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)")
|
||||||
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
|
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id")
|
@Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id")
|
||||||
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
|
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||||
|
@ -33,7 +33,13 @@ data class GradeSummary(
|
|||||||
@ColumnInfo(name = "points_sum")
|
@ColumnInfo(name = "points_sum")
|
||||||
val pointsSum: String,
|
val pointsSum: String,
|
||||||
|
|
||||||
val average: Double
|
@ColumnInfo(name = "points_sum_all_year")
|
||||||
|
val pointsSumAllYear: String?,
|
||||||
|
|
||||||
|
val average: Double,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "average_all_year")
|
||||||
|
val averageAllYear: Double? = null,
|
||||||
) {
|
) {
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
@ -9,8 +9,8 @@ import java.time.Instant
|
|||||||
@Entity(tableName = "MobileDevices")
|
@Entity(tableName = "MobileDevices")
|
||||||
data class MobileDevice(
|
data class MobileDevice(
|
||||||
|
|
||||||
@ColumnInfo(name = "user_login_id")
|
@ColumnInfo(name = "user_login_id") // todo: change column name
|
||||||
val userLoginId: Int,
|
val studentId: Int,
|
||||||
|
|
||||||
@ColumnInfo(name = "device_id")
|
@ColumnInfo(name = "device_id")
|
||||||
val deviceId: Int,
|
val deviceId: Int,
|
||||||
|
@ -9,8 +9,8 @@ import java.time.LocalDate
|
|||||||
@Entity(tableName = "SchoolAnnouncements")
|
@Entity(tableName = "SchoolAnnouncements")
|
||||||
data class SchoolAnnouncement(
|
data class SchoolAnnouncement(
|
||||||
|
|
||||||
@ColumnInfo(name = "user_login_id")
|
@ColumnInfo(name = "user_login_id") // todo: change column name
|
||||||
val userLoginId: Int,
|
val studentId: Int,
|
||||||
|
|
||||||
val date: LocalDate,
|
val date: LocalDate,
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ data class Student(
|
|||||||
@ColumnInfo(name = "student_id")
|
@ColumnInfo(name = "student_id")
|
||||||
val studentId: Int,
|
val studentId: Int,
|
||||||
|
|
||||||
|
@Deprecated("not available in VULCAN anymore")
|
||||||
@ColumnInfo(name = "user_login_id")
|
@ColumnInfo(name = "user_login_id")
|
||||||
val userLoginId: Int,
|
val userLoginId: Int,
|
||||||
|
|
||||||
@ -82,8 +83,8 @@ data class Student(
|
|||||||
@ColumnInfo(name = "is_authorized", defaultValue = "0")
|
@ColumnInfo(name = "is_authorized", defaultValue = "0")
|
||||||
val isAuthorized: Boolean,
|
val isAuthorized: Boolean,
|
||||||
|
|
||||||
@ColumnInfo(name = "is_edu_one", defaultValue = "0")
|
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
|
||||||
val isEduOne: Boolean,
|
val isEduOne: Boolean?,
|
||||||
|
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
@ -95,3 +96,22 @@ data class Student(
|
|||||||
@ColumnInfo(name = "avatar_color")
|
@ColumnInfo(name = "avatar_color")
|
||||||
var avatarColor = 0L
|
var avatarColor = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class StudentIsAuthorized(
|
||||||
|
|
||||||
|
@PrimaryKey
|
||||||
|
var id: Long,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_authorized", defaultValue = "NULL")
|
||||||
|
val isAuthorized: Boolean?,
|
||||||
|
) : Serializable
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class StudentIsEduOne(
|
||||||
|
@PrimaryKey
|
||||||
|
var id: Long,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
|
||||||
|
val isEduOne: Boolean?,
|
||||||
|
) : Serializable
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.db.entities
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class StudentIsAuthorized(
|
|
||||||
@ColumnInfo(name = "is_authorized", defaultValue = "0")
|
|
||||||
val isAuthorized: Boolean,
|
|
||||||
) : Serializable {
|
|
||||||
|
|
||||||
@PrimaryKey
|
|
||||||
var id: Long = 0
|
|
||||||
}
|
|
@ -0,0 +1,11 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.AutoMigrationSpec
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration63 : AutoMigrationSpec {
|
||||||
|
|
||||||
|
override fun onPostMigrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0")
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement
|
|||||||
@JvmName("mapDirectorInformationToEntities")
|
@JvmName("mapDirectorInformationToEntities")
|
||||||
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
||||||
SchoolAnnouncement(
|
SchoolAnnouncement(
|
||||||
userLoginId = student.userLoginId,
|
studentId = student.studentId,
|
||||||
date = it.date,
|
date = it.date,
|
||||||
subject = it.subject,
|
subject = it.subject,
|
||||||
content = it.content,
|
content = it.content,
|
||||||
@ -19,7 +19,7 @@ fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
|||||||
@JvmName("mapLastAnnouncementsToEntities")
|
@JvmName("mapLastAnnouncementsToEntities")
|
||||||
fun List<SdkLastAnnouncement>.mapToEntities(student: Student) = map {
|
fun List<SdkLastAnnouncement>.mapToEntities(student: Student) = map {
|
||||||
SchoolAnnouncement(
|
SchoolAnnouncement(
|
||||||
userLoginId = student.userLoginId,
|
studentId = student.studentId,
|
||||||
date = it.date,
|
date = it.date,
|
||||||
subject = it.subject,
|
subject = it.subject,
|
||||||
content = it.content,
|
content = it.content,
|
||||||
|
@ -37,9 +37,11 @@ fun List<SdkGradeSummary>.mapToEntities(semester: Semester) = map {
|
|||||||
predictedGrade = it.predicted,
|
predictedGrade = it.predicted,
|
||||||
finalGrade = it.final,
|
finalGrade = it.final,
|
||||||
pointsSum = it.pointsSum,
|
pointsSum = it.pointsSum,
|
||||||
|
pointsSumAllYear = it.pointsSumAllYear,
|
||||||
proposedPoints = it.proposedPoints,
|
proposedPoints = it.proposedPoints,
|
||||||
finalPoints = it.finalPoints,
|
finalPoints = it.finalPoints,
|
||||||
average = it.average
|
average = it.average,
|
||||||
|
averageAllYear = it.averageAllYear,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken
|
|||||||
|
|
||||||
fun List<SdkDevice>.mapToEntities(student: Student) = map {
|
fun List<SdkDevice>.mapToEntities(student: Student) = map {
|
||||||
MobileDevice(
|
MobileDevice(
|
||||||
userLoginId = student.userLoginId,
|
studentId = student.studentId,
|
||||||
date = it.createDate.toInstant(),
|
date = it.createDate.toInstant(),
|
||||||
deviceId = it.id,
|
deviceId = it.id,
|
||||||
name = it.name
|
name = it.name
|
||||||
|
@ -38,7 +38,7 @@ class MobileDeviceRepository @Inject constructor(
|
|||||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { mobileDb.loadAll(student.userLoginId) },
|
query = { mobileDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
wulkanowySdkFactory.create(student, semester)
|
wulkanowySdkFactory.create(student, semester)
|
||||||
.getRegisteredDevices()
|
.getRegisteredDevices()
|
||||||
|
@ -37,7 +37,7 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
it.isEmpty() || forceRefresh || isExpired
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(student.userLoginId)
|
schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
val sdk = wulkanowySdkFactory.create(student)
|
val sdk = wulkanowySdkFactory.create(student)
|
||||||
@ -57,7 +57,7 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.userLoginId)
|
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||||
|
@ -64,7 +64,10 @@ class SemesterRepository @Inject constructor(
|
|||||||
.getSemesters()
|
.getSemesters()
|
||||||
.mapToEntities(student.studentId)
|
.mapToEntities(student.studentId)
|
||||||
|
|
||||||
if (new.isEmpty()) return Timber.i("Empty semester list!")
|
if (new.isEmpty()) {
|
||||||
|
Timber.i("Empty semester list from SDK!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val old = semesterDb.loadAll(student.studentId, student.classId)
|
val old = semesterDb.loadAll(student.studentId, student.classId)
|
||||||
semesterDb.removeOldAndSaveNew(
|
semesterDb.removeOldAndSaveNew(
|
||||||
|
@ -12,13 +12,14 @@ import io.github.wulkanowy.data.db.entities.StudentName
|
|||||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.mappers.mapToPojo
|
import io.github.wulkanowy.data.mappers.mapToPojo
|
||||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
|
||||||
import io.github.wulkanowy.utils.security.Scrambler
|
import io.github.wulkanowy.utils.security.Scrambler
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ class StudentRepository @Inject constructor(
|
|||||||
): RegisterUser = wulkanowySdkFactory.create()
|
): RegisterUser = wulkanowySdkFactory.create()
|
||||||
.getStudentsFromHebe(token, pin, symbol, "")
|
.getStudentsFromHebe(token, pin, symbol, "")
|
||||||
.mapToPojo(null)
|
.mapToPojo(null)
|
||||||
|
.also { it.logErrors() }
|
||||||
|
|
||||||
suspend fun getUserSubjectsFromScrapper(
|
suspend fun getUserSubjectsFromScrapper(
|
||||||
email: String,
|
email: String,
|
||||||
@ -51,6 +53,7 @@ class StudentRepository @Inject constructor(
|
|||||||
): RegisterUser = wulkanowySdkFactory.create()
|
): RegisterUser = wulkanowySdkFactory.create()
|
||||||
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
|
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
|
||||||
.mapToPojo(password)
|
.mapToPojo(password)
|
||||||
|
.also { it.logErrors() }
|
||||||
|
|
||||||
suspend fun getStudentsHybrid(
|
suspend fun getStudentsHybrid(
|
||||||
email: String,
|
email: String,
|
||||||
@ -60,6 +63,7 @@ class StudentRepository @Inject constructor(
|
|||||||
): RegisterUser = wulkanowySdkFactory.create()
|
): RegisterUser = wulkanowySdkFactory.create()
|
||||||
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||||
.mapToPojo(password)
|
.mapToPojo(password)
|
||||||
|
.also { it.logErrors() }
|
||||||
|
|
||||||
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> {
|
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> {
|
||||||
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
|
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
|
||||||
@ -101,23 +105,44 @@ class StudentRepository @Inject constructor(
|
|||||||
return student
|
return student
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun checkCurrentStudentAuthorizationStatus() {
|
suspend fun updateCurrentStudentAuthStatus() {
|
||||||
|
Timber.i("Check isAuthorized: started")
|
||||||
val student = getCurrentStudent()
|
val student = getCurrentStudent()
|
||||||
|
if (student.isAuthorized) {
|
||||||
if (!student.isAuthorized) {
|
Timber.i("Check isAuthorized: already authorized")
|
||||||
val currentSemester = semesterDb.loadAll(
|
return
|
||||||
studentId = student.studentId,
|
|
||||||
classId = student.classId,
|
|
||||||
).getCurrentOrLast()
|
|
||||||
val initializedSdk = wulkanowySdkFactory.create(student, currentSemester)
|
|
||||||
val isAuthorized = initializedSdk.getCurrentStudent()?.isAuthorized ?: false
|
|
||||||
|
|
||||||
if (isAuthorized) {
|
|
||||||
studentDb.update(StudentIsAuthorized(isAuthorized = true).apply {
|
|
||||||
id = student.id
|
|
||||||
})
|
|
||||||
} else throw NoAuthorizationException()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val initializedSdk = wulkanowySdkFactory.create(student)
|
||||||
|
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
|
||||||
|
.onFailure { Timber.e(it, "Check isAuthorized: error occurred") }
|
||||||
|
.getOrNull()
|
||||||
|
|
||||||
|
if (newCurrentStudent == null) {
|
||||||
|
Timber.d("Check isAuthorized: current user is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||||
|
if (currentStudentSemesters.isEmpty()) {
|
||||||
|
Timber.d("Check isAuthorized: apply empty semesters workaround")
|
||||||
|
semesterDb.insertSemesters(
|
||||||
|
items = newCurrentStudent.semesters.mapToEntities(student.studentId),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newCurrentStudent.isAuthorized) {
|
||||||
|
Timber.i("Check isAuthorized: authorization required")
|
||||||
|
throw NoAuthorizationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val studentIsAuthorized = StudentIsAuthorized(
|
||||||
|
id = student.id,
|
||||||
|
isAuthorized = true
|
||||||
|
)
|
||||||
|
|
||||||
|
Timber.i("Check isAuthorized: already authorized, update local status")
|
||||||
|
studentDb.update(studentIsAuthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {
|
||||||
@ -172,15 +197,21 @@ class StudentRepository @Inject constructor(
|
|||||||
wulkanowySdkFactory.create(student, semester)
|
wulkanowySdkFactory.create(student, semester)
|
||||||
.authorizePermission(pesel)
|
.authorizePermission(pesel)
|
||||||
|
|
||||||
suspend fun refreshStudentName(student: Student, semester: Semester) {
|
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
|
||||||
val newCurrentApiStudent = wulkanowySdkFactory.create(student, semester)
|
val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
|
||||||
.getCurrentStudent() ?: return
|
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
|
||||||
|
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
|
||||||
|
.getOrNull() ?: return
|
||||||
|
|
||||||
val studentName = StudentName(
|
val studentName = StudentName(
|
||||||
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
|
studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}"
|
||||||
).apply { id = student.id }
|
).apply { id = student.id }
|
||||||
|
|
||||||
studentDb.update(studentName)
|
studentDb.update(studentName)
|
||||||
|
semesterDb.removeOldAndSaveNew(
|
||||||
|
oldItems = semesterDb.loadAll(student.studentId, semester.classId),
|
||||||
|
newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
||||||
@ -193,7 +224,18 @@ class StudentRepository @Inject constructor(
|
|||||||
appDatabase.clearAllTables()
|
appDatabase.clearAllTables()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun RegisterUser.logErrors() {
|
||||||
|
val symbolsErrors = symbols.filter { it.error != null }
|
||||||
|
.map { it.error }
|
||||||
|
val unitsErrors = symbols.flatMap { it.schools }
|
||||||
|
.filter { it.error != null }
|
||||||
|
.map { it.error }
|
||||||
|
|
||||||
|
(symbolsErrors + unitsErrors).forEach { error ->
|
||||||
|
Timber.e(error, "Error occurred while fetching students")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoAuthorizationException : Exception()
|
class NoAuthorizationException : Exception()
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ 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.sdk.exception.FeatureNotAvailableException
|
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||||
|
import io.github.wulkanowy.sdk.scrapper.exception.FeatureUnavailableException
|
||||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||||
import io.github.wulkanowy.services.sync.works.Work
|
import io.github.wulkanowy.services.sync.works.Work
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
@ -48,6 +49,7 @@ class SyncWorker @AssistedInject constructor(
|
|||||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||||
student to semester
|
student to semester
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e)
|
||||||
return@withContext getResultFromErrors(listOf(e))
|
return@withContext getResultFromErrors(listOf(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ class SyncWorker @AssistedInject constructor(
|
|||||||
null
|
null
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
if (e is FeatureDisabledException || e is FeatureNotAvailableException || e is FeatureUnavailableException) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.calculator
|
package io.github.wulkanowy.ui.modules.attendance.calculator
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -9,7 +12,9 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.pojos.AttendanceData
|
import io.github.wulkanowy.data.pojos.AttendanceData
|
||||||
import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding
|
import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -33,6 +38,12 @@ class AttendanceCalculatorFragment :
|
|||||||
|
|
||||||
override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty()
|
override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty()
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding = FragmentAttendanceCalculatorBinding.bind(view)
|
binding = FragmentAttendanceCalculatorBinding.bind(view)
|
||||||
@ -40,6 +51,19 @@ class AttendanceCalculatorFragment :
|
|||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.action_menu_attendance_calculator, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return if (item.itemId == R.id.attendance_calculator_menu_settings) presenter.onSettingsSelected()
|
||||||
|
else false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openSettingsView() {
|
||||||
|
(activity as? MainActivity)?.pushView(AppearanceFragment.withFocusedPreference(getString(R.string.pref_key_attendance_target)))
|
||||||
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(binding.attendanceCalculatorRecycler) {
|
with(binding.attendanceCalculatorRecycler) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
@ -50,7 +74,11 @@ class AttendanceCalculatorFragment :
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||||
attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh))
|
attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor(
|
||||||
|
requireContext().getThemeAttrColor(
|
||||||
|
R.attr.colorSwipeRefresh
|
||||||
|
)
|
||||||
|
)
|
||||||
attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() }
|
attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.calculator
|
package io.github.wulkanowy.ui.modules.attendance.calculator
|
||||||
|
|
||||||
import io.github.wulkanowy.data.*
|
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.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.domain.attendance.GetAttendanceCalculatorDataUseCase
|
import io.github.wulkanowy.domain.attendance.GetAttendanceCalculatorDataUseCase
|
||||||
@ -81,4 +86,9 @@ class AttendanceCalculatorPresenter @Inject constructor(
|
|||||||
} else showError(message, error)
|
} else showError(message, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSettingsSelected(): Boolean {
|
||||||
|
view?.openSettingsView()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,4 +26,6 @@ interface AttendanceCalculatorView : BaseView {
|
|||||||
fun updateData(data: List<AttendanceData>)
|
fun updateData(data: List<AttendanceData>)
|
||||||
|
|
||||||
fun clearView()
|
fun clearView()
|
||||||
|
|
||||||
|
fun openSettingsView()
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository
|
|||||||
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 kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AuthPresenter @Inject constructor(
|
class AuthPresenter @Inject constructor(
|
||||||
@ -26,8 +27,12 @@ class AuthPresenter @Inject constructor(
|
|||||||
|
|
||||||
private fun loadName() {
|
private fun loadName() {
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
runCatching { studentRepository.getCurrentStudent(false) }
|
runCatching {
|
||||||
.onSuccess { view?.showDescriptionWithName(it.studentName) }
|
studentRepository.getCurrentStudent(false)
|
||||||
|
.studentName
|
||||||
|
.replace(" ", "\u00A0")
|
||||||
|
}
|
||||||
|
.onSuccess { view?.showDescriptionWithName(it) }
|
||||||
.onFailure { errorHandler.dispatch(it) }
|
.onFailure { errorHandler.dispatch(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,8 +62,9 @@ class AuthPresenter @Inject constructor(
|
|||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
|
|
||||||
val isSuccess = studentRepository.authorizePermission(student, semester, pesel)
|
val isSuccess = studentRepository.authorizePermission(student, semester, pesel)
|
||||||
|
Timber.d("Auth succeed: $isSuccess")
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
studentRepository.refreshStudentName(student, semester)
|
studentRepository.refreshStudentAfterAuthorize(student, semester)
|
||||||
}
|
}
|
||||||
isSuccess
|
isSuccess
|
||||||
}
|
}
|
||||||
@ -68,6 +74,7 @@ class AuthPresenter @Inject constructor(
|
|||||||
view?.showContent(true)
|
view?.showContent(true)
|
||||||
}
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
|
Timber.d("Auth fully succeed: $it")
|
||||||
if (it) {
|
if (it) {
|
||||||
view?.showSuccess(true)
|
view?.showSuccess(true)
|
||||||
view?.showContent(false)
|
view?.showContent(false)
|
||||||
|
@ -26,5 +26,7 @@ private fun generateSummary(subject: String, predicted: String, final: String) =
|
|||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
average = .0
|
average = .0,
|
||||||
|
pointsSumAllYear = null,
|
||||||
|
averageAllYear = null,
|
||||||
)
|
)
|
||||||
|
@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf(
|
|||||||
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
|
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
userLoginId = 0,
|
studentId = 0,
|
||||||
date = LocalDate.now()
|
date = LocalDate.now()
|
||||||
)
|
)
|
||||||
|
@ -266,7 +266,9 @@ class GradeAverageProvider @Inject constructor(
|
|||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
average = .0
|
pointsSumAllYear = null,
|
||||||
|
average = .0,
|
||||||
|
averageAllYear = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,13 +296,15 @@ class GradeAverageProvider @Inject constructor(
|
|||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
|
pointsSumAllYear = null,
|
||||||
average = when {
|
average = when {
|
||||||
calcAverage -> details
|
calcAverage -> details
|
||||||
.updateModifiers(student, params)
|
.updateModifiers(student, params)
|
||||||
.calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
|
.calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
|
||||||
|
|
||||||
else -> .0
|
else -> .0
|
||||||
}
|
},
|
||||||
|
averageAllYear = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
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 com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@ -31,14 +30,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: GradePresenter
|
lateinit var presenter: GradePresenter
|
||||||
|
|
||||||
private val pagerAdapter by lazy {
|
|
||||||
BaseFragmentPagerAdapter(
|
|
||||||
fragmentManager = childFragmentManager,
|
|
||||||
pagesCount = 3,
|
|
||||||
lifecycle = lifecycle,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var semesterSwitchMenu: MenuItem? = null
|
private var semesterSwitchMenu: MenuItem? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -52,6 +43,8 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
|||||||
|
|
||||||
override val currentPageIndex get() = binding.gradeViewPager.currentItem
|
override val currentPageIndex get() = binding.gradeViewPager.currentItem
|
||||||
|
|
||||||
|
private var pagerAdapter: BaseFragmentPagerAdapter? = null
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -71,13 +64,26 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
|
with(binding) {
|
||||||
|
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
|
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initTabs(pageCount: Int) {
|
||||||
|
pagerAdapter = BaseFragmentPagerAdapter(
|
||||||
|
lifecycle = lifecycle,
|
||||||
|
pagesCount = pageCount,
|
||||||
|
fragmentManager = childFragmentManager
|
||||||
|
)
|
||||||
|
|
||||||
with(binding.gradeViewPager) {
|
with(binding.gradeViewPager) {
|
||||||
adapter = pagerAdapter
|
adapter = pagerAdapter
|
||||||
offscreenPageLimit = 3
|
offscreenPageLimit = 3
|
||||||
setOnSelectPageListener(presenter::onPageSelected)
|
setOnSelectPageListener(presenter::onPageSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(pagerAdapter) {
|
with(pagerAdapter!!) {
|
||||||
containerId = binding.gradeViewPager.id
|
containerId = binding.gradeViewPager.id
|
||||||
titleFactory = {
|
titleFactory = {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -99,11 +105,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
|
binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
|
||||||
|
|
||||||
with(binding) {
|
|
||||||
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
|
|
||||||
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
@ -169,19 +170,20 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
|
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
|
||||||
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)
|
(pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)
|
||||||
?.onParentLoadData(semesterId, forceRefresh)
|
?.onParentLoadData(semesterId, forceRefresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyChildParentReselected(index: Int) {
|
override fun notifyChildParentReselected(index: Int) {
|
||||||
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
|
(pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyChildSemesterChange(index: Int) {
|
override fun notifyChildSemesterChange(index: Int) {
|
||||||
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
|
(pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
pagerAdapter = null
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,8 @@ class GradePresenter @Inject constructor(
|
|||||||
) : BasePresenter<GradeView>(errorHandler, studentRepository) {
|
) : BasePresenter<GradeView>(errorHandler, studentRepository) {
|
||||||
|
|
||||||
private var selectedIndex = 0
|
private var selectedIndex = 0
|
||||||
|
|
||||||
private var schoolYear = 0
|
private var schoolYear = 0
|
||||||
|
private var availableSemesters = emptyList<Semester>()
|
||||||
private var semesters = emptyList<Semester>()
|
|
||||||
|
|
||||||
private val loadedSemesterId = mutableMapOf<Int, Int>()
|
private val loadedSemesterId = mutableMapOf<Int, Int>()
|
||||||
|
|
||||||
private lateinit var lastError: Throwable
|
private lateinit var lastError: Throwable
|
||||||
@ -40,7 +37,7 @@ class GradePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onCreateMenu() {
|
fun onCreateMenu() {
|
||||||
if (semesters.isEmpty()) view?.showSemesterSwitch(false)
|
if (availableSemesters.isEmpty()) view?.showSemesterSwitch(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewReselected() {
|
fun onViewReselected() {
|
||||||
@ -49,8 +46,8 @@ class GradePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onSemesterSwitch(): Boolean {
|
fun onSemesterSwitch(): Boolean {
|
||||||
if (semesters.isNotEmpty()) {
|
if (availableSemesters.isNotEmpty()) {
|
||||||
view?.showSemesterDialog(selectedIndex - 1, semesters.take(2))
|
view?.showSemesterDialog(selectedIndex - 1, availableSemesters.take(2))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -83,7 +80,7 @@ class GradePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onPageSelected(index: Int) {
|
fun onPageSelected(index: Int) {
|
||||||
if (semesters.isNotEmpty()) loadChild(index)
|
if (availableSemesters.isNotEmpty()) loadChild(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRetry() {
|
fun onRetry() {
|
||||||
@ -101,16 +98,24 @@ class GradePresenter @Inject constructor(
|
|||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
resourceFlow {
|
resourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
|
val semesters = semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
|
||||||
|
|
||||||
|
student to semesters
|
||||||
}
|
}
|
||||||
.logResourceStatus("load grade data")
|
.logResourceStatus("load grade data")
|
||||||
.onResourceData {
|
.onResourceData { (student, semesters) ->
|
||||||
val current = it.getCurrentOrLast()
|
val currentSemester = semesters.getCurrentOrLast()
|
||||||
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
|
selectedIndex =
|
||||||
schoolYear = current.schoolYear
|
if (selectedIndex == 0) currentSemester.semesterName else selectedIndex
|
||||||
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
|
schoolYear = currentSemester.schoolYear
|
||||||
view?.setCurrentSemesterName(current.semesterName, schoolYear)
|
availableSemesters = semesters.filter { semester ->
|
||||||
|
semester.diaryId == currentSemester.diaryId
|
||||||
|
}
|
||||||
|
|
||||||
view?.run {
|
view?.run {
|
||||||
|
initTabs(if (student.isEduOne == true) 2 else 3)
|
||||||
|
setCurrentSemesterName(currentSemester.semesterName, schoolYear)
|
||||||
|
|
||||||
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
|
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
|
||||||
loadChild(currentPageIndex)
|
loadChild(currentPageIndex)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
@ -131,10 +136,10 @@ class GradePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
||||||
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}")
|
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${availableSemesters.joinToString { it.semesterName.toString() }}")
|
||||||
|
|
||||||
val newSelectedSemesterId = try {
|
val newSelectedSemesterId = try {
|
||||||
semesters.first { it.semesterName == selectedIndex }.semesterId
|
availableSemesters.first { it.semesterName == selectedIndex }.semesterId
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
Timber.e(e, "Selected semester no exists")
|
Timber.e(e, "Selected semester no exists")
|
||||||
return
|
return
|
||||||
|
@ -9,6 +9,8 @@ interface GradeView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
|
fun initTabs(pageCount: Int)
|
||||||
|
|
||||||
fun showContent(show: Boolean)
|
fun showContent(show: Boolean)
|
||||||
|
|
||||||
fun showProgress(show: Boolean)
|
fun showProgress(show: Boolean)
|
||||||
|
@ -96,9 +96,11 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
|||||||
ViewType.HEADER.id -> HeaderViewHolder(
|
ViewType.HEADER.id -> HeaderViewHolder(
|
||||||
HeaderGradeDetailsBinding.inflate(inflater, parent, false)
|
HeaderGradeDetailsBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
ViewType.ITEM.id -> ItemViewHolder(
|
ViewType.ITEM.id -> ItemViewHolder(
|
||||||
ItemGradeDetailsBinding.inflate(inflater, parent, false)
|
ItemGradeDetailsBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,6 +112,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
|||||||
header = items[position].value as GradeDetailsHeader,
|
header = items[position].value as GradeDetailsHeader,
|
||||||
position = position
|
position = position
|
||||||
)
|
)
|
||||||
|
|
||||||
is ItemViewHolder -> bindItemViewHolder(
|
is ItemViewHolder -> bindItemViewHolder(
|
||||||
holder = holder,
|
holder = holder,
|
||||||
grade = items[position].value as Grade
|
grade = items[position].value as Grade
|
||||||
@ -133,6 +136,10 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
|||||||
maxLines = if (expandedPositions[headerPosition]) 2 else 1
|
maxLines = if (expandedPositions[headerPosition]) 2 else 1
|
||||||
}
|
}
|
||||||
gradeHeaderAverage.text = formatAverage(header.average, root.context.resources)
|
gradeHeaderAverage.text = formatAverage(header.average, root.context.resources)
|
||||||
|
with(gradeHeaderAverageAllYear) {
|
||||||
|
isVisible = header.averageAllYear != null && header.averageAllYear != .0
|
||||||
|
text = formatAverageAllYear(header.averageAllYear, root.context.resources)
|
||||||
|
}
|
||||||
gradeHeaderPointsSum.text =
|
gradeHeaderPointsSum.text =
|
||||||
context.getString(R.string.grade_points_sum, header.pointsSum)
|
context.getString(R.string.grade_points_sum, header.pointsSum)
|
||||||
gradeHeaderPointsSum.isVisible = !header.pointsSum.isNullOrEmpty()
|
gradeHeaderPointsSum.isVisible = !header.pointsSum.isNullOrEmpty()
|
||||||
@ -233,6 +240,13 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
|
|||||||
resources.getString(R.string.grade_average, average)
|
resources.getString(R.string.grade_average, average)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatAverageAllYear(average: Double?, resources: Resources) =
|
||||||
|
if (average == null || average == .0) {
|
||||||
|
resources.getString(R.string.grade_no_average)
|
||||||
|
} else {
|
||||||
|
resources.getString(R.string.grade_average_year, average)
|
||||||
|
}
|
||||||
|
|
||||||
private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) :
|
private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ data class GradeDetailsItem(
|
|||||||
data class GradeDetailsHeader(
|
data class GradeDetailsHeader(
|
||||||
val subject: String,
|
val subject: String,
|
||||||
val average: Double?,
|
val average: Double?,
|
||||||
|
val averageAllYear: Double?,
|
||||||
val pointsSum: String?,
|
val pointsSum: String?,
|
||||||
val grades: List<GradeDetailsItem>
|
val grades: List<GradeDetailsItem>
|
||||||
) {
|
) {
|
||||||
|
@ -226,8 +226,9 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
GradeDetailsHeader(
|
GradeDetailsHeader(
|
||||||
subject = gradeSubject.subject,
|
subject = gradeSubject.subject,
|
||||||
average = gradeSubject.average,
|
average = gradeSubject.average,
|
||||||
|
averageAllYear = gradeSubject.summary.averageAllYear,
|
||||||
pointsSum = gradeSubject.points,
|
pointsSum = gradeSubject.points,
|
||||||
grades = subItems
|
grades = subItems,
|
||||||
).apply {
|
).apply {
|
||||||
newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
|
newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size
|
||||||
}, ViewType.HEADER
|
}, ViewType.HEADER
|
||||||
|
@ -3,6 +3,7 @@ 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.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
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
|
||||||
@ -65,37 +66,55 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
val gradeSummaries = items
|
val gradeSummaries = items
|
||||||
.filter { it.gradeDescriptive == null }
|
.filter { it.gradeDescriptive == null }
|
||||||
.map { it.gradeSummary }
|
.map { it.gradeSummary }
|
||||||
|
val isSecondSemester = items.any { item ->
|
||||||
|
item.gradeSummary.let { it.averageAllYear != null && it.averageAllYear != .0 }
|
||||||
|
}
|
||||||
|
|
||||||
val context = binding.root.context
|
val context = binding.root.context
|
||||||
val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
|
val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) }
|
||||||
val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
|
val calculatedSemesterItemsCount = gradeSummaries.count { value -> value.average != 0.0 }
|
||||||
|
val calculatedAnnualItemsCount =
|
||||||
|
gradeSummaries.count { value -> value.averageAllYear != 0.0 }
|
||||||
val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
|
val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) }
|
||||||
val finalAverage = gradeSummaries.calcFinalAverage(
|
val finalAverage = gradeSummaries.calcFinalAverage(
|
||||||
preferencesRepository.gradePlusModifier,
|
plusModifier = preferencesRepository.gradePlusModifier,
|
||||||
preferencesRepository.gradeMinusModifier
|
minusModifier = preferencesRepository.gradeMinusModifier,
|
||||||
)
|
)
|
||||||
val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 }
|
val calculatedSemesterAverage = 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 }
|
.let { if (it.isNaN()) 0.0 else it }
|
||||||
|
val calculatedAnnualAverage = gradeSummaries.filter { value -> value.averageAllYear != 0.0 }
|
||||||
|
.mapNotNull { values -> values.averageAllYear }
|
||||||
|
.reversed() // fix average precision
|
||||||
|
.average()
|
||||||
|
.let { if (it.isNaN()) 0.0 else it }
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
|
gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedSemesterAverage)
|
||||||
|
gradeSummaryScrollableHeaderCalculatedAnnual.text =
|
||||||
|
formatAverage(calculatedAnnualAverage)
|
||||||
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
|
gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage)
|
||||||
gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedAverage)
|
gradeSummaryScrollableHeaderFinalSubjectCount.text = context.getString(
|
||||||
gradeSummaryScrollableHeaderFinalSubjectCount.text =
|
|
||||||
context.getString(
|
|
||||||
R.string.grade_summary_from_subjects,
|
R.string.grade_summary_from_subjects,
|
||||||
finalItemsCount,
|
finalItemsCount,
|
||||||
allItemsCount
|
allItemsCount
|
||||||
)
|
)
|
||||||
gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString(
|
gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString(
|
||||||
R.string.grade_summary_from_subjects,
|
R.string.grade_summary_from_subjects,
|
||||||
calculatedItemsCount,
|
calculatedSemesterItemsCount,
|
||||||
allItemsCount
|
allItemsCount
|
||||||
)
|
)
|
||||||
|
gradeSummaryScrollableHeaderCalculatedSubjectCountAnnual.text = context.getString(
|
||||||
|
R.string.grade_summary_from_subjects,
|
||||||
|
calculatedAnnualItemsCount,
|
||||||
|
allItemsCount
|
||||||
|
)
|
||||||
|
gradeSummaryScrollableHeaderCalculatedAnnualContainer.isVisible = isSecondSemester
|
||||||
|
|
||||||
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
|
gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() }
|
||||||
|
gradeSummaryCalculatedAverageHelpAnnual.setOnClickListener { onCalculatedHelpClickListener() }
|
||||||
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
|
gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +126,12 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
gradeSummaryItemTitle.text = gradeSummary.subject
|
gradeSummaryItemTitle.text = gradeSummary.subject
|
||||||
gradeSummaryItemPoints.text = gradeSummary.pointsSum
|
gradeSummaryItemPoints.text = gradeSummary.pointsSum
|
||||||
|
|
||||||
gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
|
gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "")
|
||||||
|
gradeSummaryItemAverageAllYear.text = gradeSummary.averageAllYear?.let {
|
||||||
|
formatAverage(it, "")
|
||||||
|
}
|
||||||
|
|
||||||
gradeSummaryItemPredicted.text =
|
gradeSummaryItemPredicted.text =
|
||||||
"${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
|
"${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim()
|
||||||
gradeSummaryItemFinal.text =
|
gradeSummaryItemFinal.text =
|
||||||
@ -116,6 +140,12 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
root.context.getString(R.string.all_no_data)
|
root.context.getString(R.string.all_no_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gradeSummaryItemAverageContainer.isVisible = gradeSummary.average != .0
|
||||||
|
gradeSummaryItemAverageDivider.isVisible = gradeSummaryItemAverageContainer.isVisible
|
||||||
|
gradeSummaryItemAverageAllYearContainer.isGone =
|
||||||
|
gradeSummary.averageAllYear == null || gradeSummary.averageAllYear == .0
|
||||||
|
gradeSummaryItemAverageAllYearDivider.isGone =
|
||||||
|
gradeSummaryItemAverageAllYearContainer.isGone
|
||||||
gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
|
gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null
|
||||||
gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
|
gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null
|
||||||
gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
|
gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null
|
||||||
@ -123,6 +153,7 @@ class GradeSummaryAdapter @Inject constructor(
|
|||||||
gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
|
gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null
|
||||||
gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
|
gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null
|
||||||
gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
|
gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank()
|
||||||
|
gradeSummaryItemPointsDivider.isVisible = gradeSummaryItemPointsContainer.isVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +98,10 @@ class HomeworkAddDialog : BaseDialogFragment<DialogHomeworkAddBinding>(), Homewo
|
|||||||
rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear,
|
rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear,
|
||||||
onDateSelected = {
|
onDateSelected = {
|
||||||
date = it
|
date = it
|
||||||
|
if (isAdded) {
|
||||||
binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString())
|
binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,19 +19,23 @@ class LoginStudentSelectAdapter @Inject constructor() :
|
|||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
return when (LoginStudentSelectItemType.values()[viewType]) {
|
return when (LoginStudentSelectItemType.entries[viewType]) {
|
||||||
LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder(
|
LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder(
|
||||||
ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
|
ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
|
||||||
)
|
)
|
||||||
|
|
||||||
LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
|
LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
|
||||||
ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
|
ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
|
LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
|
||||||
ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
|
ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
|
LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
|
||||||
ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
|
ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
LoginStudentSelectItemType.HELP -> HelpViewHolder(
|
LoginStudentSelectItemType.HELP -> HelpViewHolder(
|
||||||
ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
|
ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
@ -98,10 +102,12 @@ class LoginStudentSelectAdapter @Inject constructor() :
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
loginStudentSelectHeaderSchoolName.text = buildString {
|
loginStudentSelectHeaderSchoolName.text = buildString {
|
||||||
append(item.unit.schoolName.trim())
|
append(item.unit.schoolName.trim())
|
||||||
|
if (item.unit.schoolShortName.isNotBlank()) {
|
||||||
append(" (")
|
append(" (")
|
||||||
append(item.unit.schoolShortName)
|
append(item.unit.schoolShortName)
|
||||||
append(")")
|
append(")")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
|
loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
|
||||||
loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
|
loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
|
||||||
loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null
|
loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null
|
||||||
@ -170,9 +176,11 @@ class LoginStudentSelectAdapter @Inject constructor() :
|
|||||||
oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
|
oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
|
||||||
oldItem.symbol == newItem.symbol
|
oldItem.symbol == newItem.symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
|
oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
|
||||||
oldItem.student == newItem.student
|
oldItem.student == newItem.student
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> oldItem == newItem
|
else -> oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser
|
|||||||
import io.github.wulkanowy.data.repositories.SchoolsRepository
|
import io.github.wulkanowy.data.repositories.SchoolsRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.resourceFlow
|
import io.github.wulkanowy.data.resourceFlow
|
||||||
|
import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
@ -108,8 +109,8 @@ class LoginStudentSelectPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createItems(): List<LoginStudentSelectItem> = buildList {
|
private fun createItems(): List<LoginStudentSelectItem> = buildList {
|
||||||
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
|
val notEmptySymbols = registerUser.symbols.filter { it.shouldShowOnTop() }
|
||||||
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
|
val emptySymbols = registerUser.symbols.filter { !it.shouldShowOnTop() }
|
||||||
|
|
||||||
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
|
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) {
|
||||||
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
|
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol }))
|
||||||
@ -127,6 +128,10 @@ class LoginStudentSelectPresenter @Inject constructor(
|
|||||||
add(helpItem)
|
add(helpItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun RegisterSymbol.shouldShowOnTop(): Boolean {
|
||||||
|
return schools.isNotEmpty() || error is StudentGraduateException
|
||||||
|
}
|
||||||
|
|
||||||
private fun createNotEmptySymbolItems(
|
private fun createNotEmptySymbolItems(
|
||||||
notEmptySymbols: List<RegisterSymbol>,
|
notEmptySymbols: List<RegisterSymbol>,
|
||||||
students: List<StudentWithSemesters>,
|
students: List<StudentWithSemesters>,
|
||||||
|
@ -73,7 +73,7 @@ class MainPresenter @Inject constructor(
|
|||||||
syncManager.startPeriodicSyncWorker()
|
syncManager.startPeriodicSyncWorker()
|
||||||
|
|
||||||
checkAppSupport()
|
checkAppSupport()
|
||||||
checkCurrentStudentAuthorizationStatus()
|
updateCurrentStudentAuthStatus()
|
||||||
|
|
||||||
analytics.logEvent("app_open", "destination" to initDestination.toString())
|
analytics.logEvent("app_open", "destination" to initDestination.toString())
|
||||||
Timber.i("Main view was initialized with $initDestination")
|
Timber.i("Main view was initialized with $initDestination")
|
||||||
@ -193,12 +193,10 @@ class MainPresenter @Inject constructor(
|
|||||||
view?.showStudentAvatar(currentStudent)
|
view?.showStudentAvatar(currentStudent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkCurrentStudentAuthorizationStatus() {
|
private fun updateCurrentStudentAuthStatus() {
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
runCatching { studentRepository.checkCurrentStudentAuthorizationStatus() }
|
runCatching { studentRepository.updateCurrentStudentAuthStatus() }
|
||||||
.onFailure { errorHandler.dispatch(it) }
|
.onFailure { errorHandler.dispatch(it) }
|
||||||
|
|
||||||
Timber.i("Current student authorization status checked")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings.appearance
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.SeekBarPreference
|
import androidx.preference.SeekBarPreference
|
||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
@ -30,9 +31,18 @@ class AppearanceFragment : PreferenceFragmentCompat(),
|
|||||||
|
|
||||||
override val titleStringId get() = R.string.pref_settings_appearance_title
|
override val titleStringId get() = R.string.pref_settings_appearance_title
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun withFocusedPreference(key: String) = AppearanceFragment().apply {
|
||||||
|
arguments = bundleOf(FOCUSED_KEY to key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val FOCUSED_KEY = "focusedKey"
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
|
arguments?.getString(FOCUSED_KEY)?.let { scrollToPreference(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
@ -7,27 +7,27 @@ import android.view.ViewGroup
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableBinding
|
import io.github.wulkanowy.databinding.ItemTimetableBinding
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding
|
import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding
|
||||||
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
|
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
|
||||||
|
import io.github.wulkanowy.utils.SyncListAdapter
|
||||||
import io.github.wulkanowy.utils.getPlural
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimetableAdapter @Inject constructor() :
|
class TimetableAdapter @Inject constructor() :
|
||||||
ListAdapter<TimetableItem, RecyclerView.ViewHolder>(differ) {
|
SyncListAdapter<TimetableItem, RecyclerView.ViewHolder>(Differ) {
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
|
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
return when (TimetableItemType.values()[viewType]) {
|
return when (TimetableItemType.entries[viewType]) {
|
||||||
TimetableItemType.SMALL -> SmallViewHolder(
|
TimetableItemType.SMALL -> SmallViewHolder(
|
||||||
ItemTimetableSmallBinding.inflate(inflater, parent, false)
|
ItemTimetableSmallBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
@ -61,12 +61,10 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
binding = holder.binding,
|
binding = holder.binding,
|
||||||
item = getItem(position) as TimetableItem.Small,
|
item = getItem(position) as TimetableItem.Small,
|
||||||
)
|
)
|
||||||
|
|
||||||
is NormalViewHolder -> bindNormalView(
|
is NormalViewHolder -> bindNormalView(
|
||||||
binding = holder.binding,
|
binding = holder.binding,
|
||||||
item = getItem(position) as TimetableItem.Normal,
|
item = getItem(position) as TimetableItem.Normal,
|
||||||
)
|
)
|
||||||
|
|
||||||
is EmptyViewHolder -> bindEmptyView(
|
is EmptyViewHolder -> bindEmptyView(
|
||||||
binding = holder.binding,
|
binding = holder.binding,
|
||||||
item = getItem(position) as TimetableItem.Empty,
|
item = getItem(position) as TimetableItem.Empty,
|
||||||
@ -79,6 +77,7 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
timetableSmallItemNumber.text = lesson.number.toString()
|
timetableSmallItemNumber.text = lesson.number.toString()
|
||||||
|
timetableSmallItemNumber.isVisible = item.isLessonNumberVisible
|
||||||
timetableSmallItemSubject.text = lesson.subject
|
timetableSmallItemSubject.text = lesson.subject
|
||||||
timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
|
timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
|
||||||
timetableSmallItemRoom.text = lesson.room
|
timetableSmallItemRoom.text = lesson.room
|
||||||
@ -97,6 +96,7 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
timetableItemNumber.text = lesson.number.toString()
|
timetableItemNumber.text = lesson.number.toString()
|
||||||
|
timetableItemNumber.isVisible = item.isLessonNumberVisible
|
||||||
timetableItemSubject.text = lesson.subject
|
timetableItemSubject.text = lesson.subject
|
||||||
timetableItemGroup.text = lesson.group
|
timetableItemGroup.text = lesson.group
|
||||||
timetableItemRoom.text = lesson.room
|
timetableItemRoom.text = lesson.room
|
||||||
@ -305,8 +305,7 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) :
|
private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
companion object {
|
private object Differ : DiffUtil.ItemCallback<TimetableItem>() {
|
||||||
private val differ = object : DiffUtil.ItemCallback<TimetableItem>() {
|
|
||||||
override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
|
override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
|
||||||
when {
|
when {
|
||||||
oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
|
oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
|
||||||
@ -331,5 +330,4 @@ class TimetableAdapter @Inject constructor() :
|
|||||||
} else super.getChangePayload(oldItem, newItem)
|
} else super.getChangePayload(oldItem, newItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,11 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
|||||||
import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment
|
import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
|
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
|
||||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
|
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
|
||||||
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
|
||||||
|
import io.github.wulkanowy.utils.openMaterialDatePicker
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -104,8 +108,11 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<TimetableItem>) {
|
override fun updateData(data: List<TimetableItem>, isDayChanged: Boolean) {
|
||||||
timetableAdapter.submitList(data)
|
when {
|
||||||
|
isDayChanged -> timetableAdapter.recreate(data)
|
||||||
|
else -> timetableAdapter.submitList(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
|
@ -7,12 +7,14 @@ sealed class TimetableItem(val type: TimetableItemType) {
|
|||||||
|
|
||||||
data class Small(
|
data class Small(
|
||||||
val lesson: Timetable,
|
val lesson: Timetable,
|
||||||
|
val isLessonNumberVisible: Boolean,
|
||||||
val onClick: (Timetable) -> Unit,
|
val onClick: (Timetable) -> Unit,
|
||||||
) : TimetableItem(TimetableItemType.SMALL)
|
) : TimetableItem(TimetableItemType.SMALL)
|
||||||
|
|
||||||
data class Normal(
|
data class Normal(
|
||||||
val lesson: Timetable,
|
val lesson: Timetable,
|
||||||
val showGroupsInPlan: Boolean,
|
val showGroupsInPlan: Boolean,
|
||||||
|
val isLessonNumberVisible: Boolean,
|
||||||
val timeLeft: TimeLeft?,
|
val timeLeft: TimeLeft?,
|
||||||
val onClick: (Timetable) -> Unit,
|
val onClick: (Timetable) -> Unit,
|
||||||
) : TimetableItem(TimetableItemType.NORMAL)
|
) : TimetableItem(TimetableItemType.NORMAL)
|
||||||
|
@ -57,6 +57,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
|
|
||||||
private var initialDate: LocalDate? = null
|
private var initialDate: LocalDate? = null
|
||||||
private var isWeekendHasLessons: Boolean = false
|
private var isWeekendHasLessons: Boolean = false
|
||||||
|
private var isEduOne: Boolean = false
|
||||||
|
|
||||||
var currentDate: LocalDate? = null
|
var currentDate: LocalDate? = null
|
||||||
private set
|
private set
|
||||||
@ -80,7 +81,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
} else currentDate?.previousSchoolDay
|
} else currentDate?.previousSchoolDay
|
||||||
|
|
||||||
reloadView(date ?: return)
|
reloadView(date ?: return)
|
||||||
loadData()
|
loadData(isDayChanged = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNextDay() {
|
fun onNextDay() {
|
||||||
@ -89,7 +90,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
} else currentDate?.nextSchoolDay
|
} else currentDate?.nextSchoolDay
|
||||||
|
|
||||||
reloadView(date ?: return)
|
reloadView(date ?: return)
|
||||||
loadData()
|
loadData(isDayChanged = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPickDate() {
|
fun onPickDate() {
|
||||||
@ -103,7 +104,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
|
|
||||||
fun onSwipeRefresh() {
|
fun onSwipeRefresh() {
|
||||||
Timber.i("Force refreshing the timetable")
|
Timber.i("Force refreshing the timetable")
|
||||||
loadData(true)
|
loadData(forceRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRetry() {
|
fun onRetry() {
|
||||||
@ -111,7 +112,7 @@ class TimetablePresenter @Inject constructor(
|
|||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
showProgress(true)
|
showProgress(true)
|
||||||
}
|
}
|
||||||
loadData(true)
|
loadData(forceRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDetailsClick() {
|
fun onDetailsClick() {
|
||||||
@ -144,11 +145,12 @@ class TimetablePresenter @Inject constructor(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData(forceRefresh: Boolean = false) {
|
private fun loadData(forceRefresh: Boolean = false, isDayChanged: Boolean = false) {
|
||||||
flatResourceFlow {
|
flatResourceFlow {
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student)
|
val semester = semesterRepository.getCurrentSemester(student)
|
||||||
|
|
||||||
|
isEduOne = student.isEduOne == true
|
||||||
checkInitialAndCurrentDate(semester)
|
checkInitialAndCurrentDate(semester)
|
||||||
timetableRepository.getTimetable(
|
timetableRepository.getTimetable(
|
||||||
student = student,
|
student = student,
|
||||||
@ -167,9 +169,9 @@ class TimetablePresenter @Inject constructor(
|
|||||||
enableSwipe(true)
|
enableSwipe(true)
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
|
updateData(it.lessons, isDayChanged)
|
||||||
showContent(it.lessons.isNotEmpty())
|
showContent(it.lessons.isNotEmpty())
|
||||||
showEmpty(it.lessons.isEmpty())
|
showEmpty(it.lessons.isEmpty())
|
||||||
updateData(it.lessons)
|
|
||||||
setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content)
|
setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content)
|
||||||
reloadNavigation()
|
reloadNavigation()
|
||||||
}
|
}
|
||||||
@ -214,15 +216,14 @@ class TimetablePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateData(lessons: List<Timetable>) {
|
private fun updateData(lessons: List<Timetable>, isDayChanged: Boolean) {
|
||||||
tickTimer?.cancel()
|
tickTimer?.cancel()
|
||||||
|
|
||||||
if (currentDate != now()) {
|
view?.updateData(createItems(lessons), isDayChanged)
|
||||||
view?.updateData(createItems(lessons))
|
if (currentDate == now()) {
|
||||||
} else {
|
tickTimer = timer(period = 2_000, initialDelay = 2_000) {
|
||||||
tickTimer = timer(period = 2_000) {
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
view?.updateData(createItems(lessons))
|
view?.updateData(createItems(lessons), isDayChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,9 +235,8 @@ class TimetablePresenter @Inject constructor(
|
|||||||
if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
|
if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
|
||||||
it.isStudentPlan
|
it.isStudentPlan
|
||||||
} else true
|
} else true
|
||||||
}.sortedWith(
|
}
|
||||||
compareBy({ item -> item.number }, { item -> !item.isStudentPlan })
|
.sortedWith(compareBy({ item -> item.start }, { item -> !item.isStudentPlan }))
|
||||||
)
|
|
||||||
|
|
||||||
var prevNum = when (prefRepository.showTimetableGaps) {
|
var prevNum = when (prefRepository.showTimetableGaps) {
|
||||||
BETWEEN_AND_BEFORE_LESSONS -> 0
|
BETWEEN_AND_BEFORE_LESSONS -> 0
|
||||||
@ -257,13 +257,15 @@ class TimetablePresenter @Inject constructor(
|
|||||||
lesson = it,
|
lesson = it,
|
||||||
showGroupsInPlan = prefRepository.showGroupsInPlan,
|
showGroupsInPlan = prefRepository.showGroupsInPlan,
|
||||||
timeLeft = filteredItems.getTimeLeftForLesson(it, i),
|
timeLeft = filteredItems.getTimeLeftForLesson(it, i),
|
||||||
onClick = ::onTimetableItemSelected
|
onClick = ::onTimetableItemSelected,
|
||||||
|
isLessonNumberVisible = !isEduOne
|
||||||
)
|
)
|
||||||
add(normalLesson)
|
add(normalLesson)
|
||||||
} else {
|
} else {
|
||||||
val smallLesson = TimetableItem.Small(
|
val smallLesson = TimetableItem.Small(
|
||||||
lesson = it,
|
lesson = it,
|
||||||
onClick = ::onTimetableItemSelected
|
onClick = ::onTimetableItemSelected,
|
||||||
|
isLessonNumberVisible = !isEduOne
|
||||||
)
|
)
|
||||||
add(smallLesson)
|
add(smallLesson)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ interface TimetableView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<TimetableItem>)
|
fun updateData(data: List<TimetableItem>, isDayChanged: Boolean)
|
||||||
|
|
||||||
fun updateNavigationDay(date: String)
|
fun updateNavigationDay(date: String)
|
||||||
|
|
||||||
|
@ -155,8 +155,10 @@ class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
timePicker.addOnPositiveButtonClickListener {
|
timePicker.addOnPositiveButtonClickListener {
|
||||||
|
if (isAdded) {
|
||||||
onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
|
onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!parentFragmentManager.isStateSaved) {
|
if (!parentFragmentManager.isStateSaved) {
|
||||||
timePicker.show(parentFragmentManager, null)
|
timePicker.show(parentFragmentManager, null)
|
||||||
|
@ -46,11 +46,8 @@ class TimetableWidgetFactory(
|
|||||||
) : RemoteViewsService.RemoteViewsFactory {
|
) : RemoteViewsService.RemoteViewsFactory {
|
||||||
|
|
||||||
private var items = emptyList<TimetableWidgetItem>()
|
private var items = emptyList<TimetableWidgetItem>()
|
||||||
|
|
||||||
private var timetableCanceledColor: Int? = null
|
private var timetableCanceledColor: Int? = null
|
||||||
|
|
||||||
private var textColor: Int? = null
|
private var textColor: Int? = null
|
||||||
|
|
||||||
private var timetableChangeColor: Int? = null
|
private var timetableChangeColor: Int? = null
|
||||||
|
|
||||||
override fun getLoadingView() = null
|
override fun getLoadingView() = null
|
||||||
@ -81,7 +78,7 @@ class TimetableWidgetFactory(
|
|||||||
val lessons = getLessons(student, semester, date)
|
val lessons = getLessons(student, semester, date)
|
||||||
val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
|
val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
|
||||||
|
|
||||||
createItems(lessons, lastSync)
|
createItems(lessons, lastSync, !(student.isEduOne ?: false))
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
items = listOf(TimetableWidgetItem.Error(it))
|
items = listOf(TimetableWidgetItem.Error(it))
|
||||||
@ -113,12 +110,13 @@ class TimetableWidgetFactory(
|
|||||||
isFromAppWidget = true
|
isFromAppWidget = true
|
||||||
)
|
)
|
||||||
val lessons = timetable.toFirstResult().dataOrThrow.lessons
|
val lessons = timetable.toFirstResult().dataOrThrow.lessons
|
||||||
return lessons.sortedBy { it.number }
|
return lessons.sortedBy { it.start }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createItems(
|
private fun createItems(
|
||||||
lessons: List<Timetable>,
|
lessons: List<Timetable>,
|
||||||
lastSync: Instant?,
|
lastSync: Instant?,
|
||||||
|
isEduOne: Boolean
|
||||||
): List<TimetableWidgetItem> {
|
): List<TimetableWidgetItem> {
|
||||||
var prevNum = when (prefRepository.showTimetableGaps) {
|
var prevNum = when (prefRepository.showTimetableGaps) {
|
||||||
BETWEEN_AND_BEFORE_LESSONS -> 0
|
BETWEEN_AND_BEFORE_LESSONS -> 0
|
||||||
@ -134,7 +132,7 @@ class TimetableWidgetFactory(
|
|||||||
)
|
)
|
||||||
add(emptyItem)
|
add(emptyItem)
|
||||||
}
|
}
|
||||||
add(TimetableWidgetItem.Normal(it))
|
add(TimetableWidgetItem.Normal(it, isEduOne))
|
||||||
prevNum = it.number
|
prevNum = it.number
|
||||||
}
|
}
|
||||||
add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN))
|
add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN))
|
||||||
@ -162,9 +160,11 @@ class TimetableWidgetFactory(
|
|||||||
|
|
||||||
val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE)
|
val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE)
|
||||||
val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE)
|
val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE)
|
||||||
|
val lessonNumberVisibility = if (item.isLessonNumberVisible) VISIBLE else GONE
|
||||||
|
|
||||||
val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply {
|
val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply {
|
||||||
setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString())
|
setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString())
|
||||||
|
setViewVisibility(R.id.timetableWidgetItemNumber, lessonNumberVisibility)
|
||||||
setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime)
|
setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime)
|
||||||
setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime)
|
setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime)
|
||||||
setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject)
|
setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject)
|
||||||
|
@ -7,6 +7,7 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
|
|||||||
|
|
||||||
data class Normal(
|
data class Normal(
|
||||||
val lesson: Timetable,
|
val lesson: Timetable,
|
||||||
|
val isLessonNumberVisible: Boolean,
|
||||||
) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL)
|
) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL)
|
||||||
|
|
||||||
data class Empty(
|
data class Empty(
|
||||||
|
@ -23,7 +23,7 @@ fun getRefreshKey(name: String, semester: Semester): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshKey(name: String, student: Student): String {
|
fun getRefreshKey(name: String, student: Student): String {
|
||||||
return "${name}_${student.userLoginId}"
|
return "${name}_${student.studentId}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String {
|
fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String {
|
||||||
|
@ -18,7 +18,7 @@ fun Semester.isCurrent(now: LocalDate = now()): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun List<Semester>.getCurrentOrLast(): Semester {
|
fun List<Semester>.getCurrentOrLast(): Semester {
|
||||||
if (isEmpty()) throw RuntimeException("Empty semester list")
|
if (isEmpty()) throw IllegalStateException("Empty semester list")
|
||||||
|
|
||||||
// when there is only one current semester
|
// when there is only one current semester
|
||||||
singleOrNull { it.isCurrent() }?.let { return it }
|
singleOrNull { it.isCurrent() }?.let { return it }
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package io.github.wulkanowy.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom alternative to androidx.recyclerview.widget.ListAdapter. ListAdapter is asynchronous which
|
||||||
|
* caused data race problems in views when a Resource.Error arrived shortly after
|
||||||
|
* Resource.Intermediate/Success - occasionally in that case the user could see both the Resource's
|
||||||
|
* data and an error message one on top of the other. This is synchronized by design to avoid that
|
||||||
|
* problem, however it retains the quality of life improvements of the original.
|
||||||
|
*/
|
||||||
|
abstract class SyncListAdapter<T : Any, VH : RecyclerView.ViewHolder> private constructor(
|
||||||
|
private val updateStrategy: SyncListAdapter<T, VH>.(List<T>) -> Unit
|
||||||
|
) : RecyclerView.Adapter<VH>() {
|
||||||
|
|
||||||
|
constructor(differ: DiffUtil.ItemCallback<T>) : this({ newItems ->
|
||||||
|
val diffResult = DiffUtil.calculateDiff(toCallback(differ, items, newItems))
|
||||||
|
items = newItems
|
||||||
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
})
|
||||||
|
|
||||||
|
var items = emptyList<T>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
final override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
fun getItem(position: Int): T {
|
||||||
|
return items[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all items, same as submitList, however also disables animations temporarily.
|
||||||
|
* This prevents a flashing effect on some views. Should be used in favor of submitList when
|
||||||
|
* all data is changed (e.g. the selected day changes in timetable causing all lessons to change).
|
||||||
|
*/
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun recreate(data: List<T>) {
|
||||||
|
items = data
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitList(data: List<T>) {
|
||||||
|
updateStrategy(data.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Any> toCallback(
|
||||||
|
itemCallback: DiffUtil.ItemCallback<T>,
|
||||||
|
old: List<T>,
|
||||||
|
new: List<T>,
|
||||||
|
) = object : DiffUtil.Callback() {
|
||||||
|
override fun getOldListSize() = old.size
|
||||||
|
|
||||||
|
override fun getNewListSize() = new.size
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||||
|
itemCallback.areItemsTheSame(old[oldItemPosition], new[newItemPosition])
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||||
|
itemCallback.areContentsTheSame(old[oldItemPosition], new[newItemPosition])
|
||||||
|
|
||||||
|
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) =
|
||||||
|
itemCallback.getChangePayload(old[oldItemPosition], new[newItemPosition])
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
Wersja 2.5.2
|
Wersja 2.5.8
|
||||||
|
|
||||||
— naprawiliśmy omyłkowe wyświetlanie ekranu z wymaganą autoryzacją numerem PESEL
|
— obeszliśmy próby blokowania Wulkanowego przez firmę VULCAN, o czymś pewnie zapomnieliśmy, ale nie miejcie nam tego za złe
|
||||||
— naprawiliśmy kilka problemów ze stabilnością
|
|
||||||
— poprawiliśmy wyświetlanie kolorów we frekwencji
|
|
||||||
|
|
||||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||||
|
@ -32,12 +32,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="24dp"
|
android:layout_marginHorizontal="24dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:textSize="16sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/auth_title"
|
app:layout_constraintTop_toBottomOf="@id/auth_title"
|
||||||
app:lineHeight="24sp"
|
app:lineHeight="18sp"
|
||||||
tools:text="@string/auth_description" />
|
tools:text="@string/auth_description" />
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/auth_input_layout"
|
android:id="@+id/auth_input_layout"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
|
@ -45,13 +45,30 @@
|
|||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum"
|
app:layout_constraintEnd_toStartOf="@id/gradeHeaderAverageAllYear"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject"
|
app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
||||||
tools:text="Average: 6,00" />
|
tools:text="Average: 6,00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gradeHeaderAverageAllYear"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverage"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
||||||
|
tools:text="Roczna: 5,00"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/gradeHeaderPointsSum"
|
android:id="@+id/gradeHeaderPointsSum"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -64,7 +81,7 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toStartOf="@id/gradeHeaderNumber"
|
app:layout_constraintEnd_toStartOf="@id/gradeHeaderNumber"
|
||||||
app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverage"
|
app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverageAllYear"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
||||||
tools:text="Points: 123/200 (61,5%)" />
|
tools:text="Points: 123/200 (61,5%)" />
|
||||||
|
|
||||||
|
@ -20,20 +20,80 @@
|
|||||||
android:id="@+id/gradeSummaryItemTitle"
|
android:id="@+id/gradeSummaryItemTitle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="40dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textSize="17sp"
|
android:textSize="17sp"
|
||||||
tools:text="@tools:sample/lorem" />
|
tools:text="@tools:sample/lorem" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/gradeSummaryItemAverageContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="35dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/grade_summary_average_semester"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/gradeSummaryItemAverage"
|
android:id="@+id/gradeSummaryItemAverage"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
tools:text="4.74" />
|
tools:text="2,50" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/gradeSummaryItemAverageDivider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="@drawable/ic_all_divider" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/gradeSummaryItemAverageAllYearContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="35dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/grade_summary_average_year"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gradeSummaryItemAverageAllYear"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="4,50" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/gradeSummaryItemAverageAllYearDivider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="@drawable/ic_all_divider" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/gradeSummaryItemPointsContainer"
|
android:id="@+id/gradeSummaryItemPointsContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -63,9 +123,9 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
android:id="@+id/gradeSummaryItemPointsDivider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:id="@+id/gradeSummaryItemPointsDivider"
|
|
||||||
android:background="@drawable/ic_all_divider" />
|
android:background="@drawable/ic_all_divider" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -97,8 +157,8 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:id="@+id/gradeSummaryItemPredictedDivider"
|
android:id="@+id/gradeSummaryItemPredictedDivider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="@drawable/ic_all_divider" />
|
android:background="@drawable/ic_all_divider" />
|
||||||
|
|
||||||
@ -131,9 +191,9 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
android:id="@+id/gradeSummaryItemFinalDivider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:id="@+id/gradeSummaryItemFinalDivider"
|
|
||||||
android:background="@drawable/ic_all_divider" />
|
android:background="@drawable/ic_all_divider" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
tools:context=".ui.modules.grade.summary.GradeSummaryAdapter">
|
tools:context=".ui.modules.grade.summary.GradeSummaryAdapter">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="150dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:layout_marginEnd="15dp"
|
android:layout_marginEnd="15dp"
|
||||||
|
android:layout_weight="1"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -21,9 +22,9 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:minLines="2"
|
android:minLines="2"
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/grade_summary_calculated_average"
|
android:text="@string/grade_summary_calculated_average"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -61,9 +62,64 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="150dp"
|
android:id="@+id/gradeSummaryScrollableHeaderCalculatedAnnualContainer"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginEnd="15dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minLines="2"
|
||||||
|
android:text="@string/grade_summary_calculated_average_annual"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gradeSummaryScrollableHeaderCalculatedAnnual"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="21sp"
|
||||||
|
tools:text="6,00" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/gradeSummaryCalculatedAverageHelpAnnual"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@+string/grade_summary_calculated_average_help_dialog_title"
|
||||||
|
app:srcCompat="@drawable/ic_help"
|
||||||
|
app:tint="?colorOnBackground" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gradeSummaryScrollableHeaderCalculatedSubjectCountAnnual"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textSize="13sp"
|
||||||
|
tools:text="from 8 subjects" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_weight="1"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -71,9 +127,9 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:minLines="2"
|
android:minLines="2"
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/grade_summary_final_average"
|
android:text="@string/grade_summary_final_average"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -103,8 +159,8 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/gradeSummaryScrollableHeaderFinalSubjectCount"
|
android:id="@+id/gradeSummaryScrollableHeaderFinalSubjectCount"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
tools:text="from 5 subjects" />
|
tools:text="from 5 subjects" />
|
||||||
|
11
app/src/main/res/menu/action_menu_attendance_calculator.xml
Normal file
11
app/src/main/res/menu/action_menu_attendance_calculator.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/attendance_calculator_menu_settings"
|
||||||
|
android:icon="@drawable/ic_more_settings"
|
||||||
|
android:orderInCategory="2"
|
||||||
|
android:title="@string/pref_attendance_calculator_appearance_settings_title"
|
||||||
|
app:iconTint="?colorControlNormal"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
@ -31,7 +31,7 @@
|
|||||||
<!--Subtitles-->
|
<!--Subtitles-->
|
||||||
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
<string name="login_header_default">Přihlaste se pomocí studentského nebo rodičovského účtu</string>
|
<string name="login_header_default">Přihlaste se pomocí žákovského nebo rodičovského účtu</string>
|
||||||
<string name="login_header_symbol">Zadejte symbol ze stránky deníku: <b>%1$s</b></string>
|
<string name="login_header_symbol">Zadejte symbol ze stránky deníku: <b>%1$s</b></string>
|
||||||
<string name="login_nickname_hint">Uživatelské jméno</string>
|
<string name="login_nickname_hint">Uživatelské jméno</string>
|
||||||
<string name="login_email_hint">Email</string>
|
<string name="login_email_hint">Email</string>
|
||||||
@ -113,13 +113,17 @@
|
|||||||
<string name="grade_comment">Komentář</string>
|
<string name="grade_comment">Komentář</string>
|
||||||
<string name="grade_number_new_items">Počet nových známek: %1$d</string>
|
<string name="grade_number_new_items">Počet nových známek: %1$d</string>
|
||||||
<string name="grade_average">Průměr: %1$.2f</string>
|
<string name="grade_average">Průměr: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Annual: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Body: %s</string>
|
<string name="grade_points_sum">Body: %s</string>
|
||||||
<string name="grade_no_average">Bez průměru</string>
|
<string name="grade_no_average">Bez průměru</string>
|
||||||
|
<string name="grade_summary_average_semester">Semester average</string>
|
||||||
|
<string name="grade_summary_average_year">Annual average</string>
|
||||||
<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_descriptive">Popisná známka</string>
|
||||||
<string name="grade_summary_calculated_average">Vypočítaný průměr</string>
|
<string name="grade_summary_calculated_average">Calculated semester average</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Calculated annual average</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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">Jak funguje konečný průměr?</string>
|
<string name="grade_summary_final_average_help_dialog_title">Jak funguje konečný průměr?</string>
|
||||||
@ -270,6 +274,7 @@
|
|||||||
<string name="attendance_calculator_summary_balance_neutral">přesně v cíli</string>
|
<string name="attendance_calculator_summary_balance_neutral">přesně v cíli</string>
|
||||||
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> pod cílem</string>
|
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> pod cílem</string>
|
||||||
<string name="attendance_calculator_summary_values">%1$d/%2$d přítomnosti</string>
|
<string name="attendance_calculator_summary_values">%1$d/%2$d přítomnosti</string>
|
||||||
|
<string name="attendance_calculator_summary_values_empty">Nebyla zaznamenána žádná docházka</string>
|
||||||
<string name="attendance_absence_school">Nepřítomnost ze školních důvodů</string>
|
<string name="attendance_absence_school">Nepřítomnost ze školních důvodů</string>
|
||||||
<string name="attendance_absence_excused">Omluvená nepřítomnost</string>
|
<string name="attendance_absence_excused">Omluvená nepřítomnost</string>
|
||||||
<string name="attendance_absence_unexcused">Neomluvená nepřítomnost</string>
|
<string name="attendance_absence_unexcused">Neomluvená nepřítomnost</string>
|
||||||
@ -737,6 +742,7 @@
|
|||||||
<string name="pref_view_grade_average_force_calc">Vynutit průměrný výpočet podle aplikace</string>
|
<string name="pref_view_grade_average_force_calc">Vynutit průměrný výpočet podle aplikace</string>
|
||||||
<string name="pref_view_present">Zobrazit přítomnost</string>
|
<string name="pref_view_present">Zobrazit přítomnost</string>
|
||||||
<string name="pref_attendance_target">Cílová docházka</string>
|
<string name="pref_attendance_target">Cílová docházka</string>
|
||||||
|
<string name="pref_attendance_calculator_show_empty_subjects">Zobrazit předměty bez docházek</string>
|
||||||
<string name="pref_view_attendance_calculator_sorting_mode">Třídění kalkulačky docházky</string>
|
<string name="pref_view_attendance_calculator_sorting_mode">Třídění kalkulačky docházky</string>
|
||||||
<string name="pref_view_app_theme">Motiv</string>
|
<string name="pref_view_app_theme">Motiv</string>
|
||||||
<string name="pref_view_expand_grade">Rozvíjení známek</string>
|
<string name="pref_view_expand_grade">Rozvíjení známek</string>
|
||||||
@ -856,7 +862,7 @@
|
|||||||
<string name="auth_button">Autorizovat</string>
|
<string name="auth_button">Autorizovat</string>
|
||||||
<string name="auth_success">Autorizace byla úspěšně dokončena</string>
|
<string name="auth_success">Autorizace byla úspěšně dokončena</string>
|
||||||
<string name="auth_title">Autorizace</string>
|
<string name="auth_title">Autorizace</string>
|
||||||
<string name="auth_description">Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli</string>
|
<string name="auth_description">Dear Parent,<br/><br/>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br/><br/>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br/><br/>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br/><br/>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.</string>
|
||||||
<string name="auth_button_skip">Zatím přeskočit</string>
|
<string name="auth_button_skip">Zatím přeskočit</string>
|
||||||
<!--Captcha-->
|
<!--Captcha-->
|
||||||
<string name="captcha_dialog_title">Webová stránka deníku VULCAN vyžaduje ověření</string>
|
<string name="captcha_dialog_title">Webová stránka deníku VULCAN vyžaduje ověření</string>
|
||||||
|
@ -113,13 +113,17 @@
|
|||||||
<string name="grade_comment">Kommentar</string>
|
<string name="grade_comment">Kommentar</string>
|
||||||
<string name="grade_number_new_items">Anzahl der neuen Bewertungen: %1$d</string>
|
<string name="grade_number_new_items">Anzahl der neuen Bewertungen: %1$d</string>
|
||||||
<string name="grade_average">Durchschnitt: %1$.2f</string>
|
<string name="grade_average">Durchschnitt: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Annual: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Punkte: %s</string>
|
<string name="grade_points_sum">Punkte: %s</string>
|
||||||
<string name="grade_no_average">Kein Durchschnitt</string>
|
<string name="grade_no_average">Kein Durchschnitt</string>
|
||||||
|
<string name="grade_summary_average_semester">Semester average</string>
|
||||||
|
<string name="grade_summary_average_year">Annual average</string>
|
||||||
<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_descriptive">Descriptive grade</string>
|
||||||
<string name="grade_summary_calculated_average">Berechnender Durchschnitt</string>
|
<string name="grade_summary_calculated_average">Calculated semester average</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Calculated annual average</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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">Wie funktioniert der endgültige Durchschnitt?</string>
|
<string name="grade_summary_final_average_help_dialog_title">Wie funktioniert der endgültige Durchschnitt?</string>
|
||||||
@ -242,6 +246,7 @@
|
|||||||
<string name="attendance_calculator_summary_balance_neutral">right on target</string>
|
<string name="attendance_calculator_summary_balance_neutral">right on target</string>
|
||||||
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> under target</string>
|
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> under target</string>
|
||||||
<string name="attendance_calculator_summary_values">%1$d/%2$d presences</string>
|
<string name="attendance_calculator_summary_values">%1$d/%2$d presences</string>
|
||||||
|
<string name="attendance_calculator_summary_values_empty">No attendances recorded</string>
|
||||||
<string name="attendance_absence_school">Aus schulischen Gründen abwesend</string>
|
<string name="attendance_absence_school">Aus schulischen Gründen abwesend</string>
|
||||||
<string name="attendance_absence_excused">Entschuldigte Abwesenheit</string>
|
<string name="attendance_absence_excused">Entschuldigte Abwesenheit</string>
|
||||||
<string name="attendance_absence_unexcused">Unentschuldigtes Abwesenheit</string>
|
<string name="attendance_absence_unexcused">Unentschuldigtes Abwesenheit</string>
|
||||||
@ -643,6 +648,7 @@
|
|||||||
<string name="pref_view_grade_average_force_calc">Mittelwertberechnung durch App erzwingen</string>
|
<string name="pref_view_grade_average_force_calc">Mittelwertberechnung durch App erzwingen</string>
|
||||||
<string name="pref_view_present">Anwesendheit zeigen</string>
|
<string name="pref_view_present">Anwesendheit zeigen</string>
|
||||||
<string name="pref_attendance_target">Attendance target</string>
|
<string name="pref_attendance_target">Attendance target</string>
|
||||||
|
<string name="pref_attendance_calculator_show_empty_subjects">Show subjects without any attendances</string>
|
||||||
<string name="pref_view_attendance_calculator_sorting_mode">Attendance calculator sorting</string>
|
<string name="pref_view_attendance_calculator_sorting_mode">Attendance calculator sorting</string>
|
||||||
<string name="pref_view_app_theme">Thema</string>
|
<string name="pref_view_app_theme">Thema</string>
|
||||||
<string name="pref_view_expand_grade">Steigende Sorten</string>
|
<string name="pref_view_expand_grade">Steigende Sorten</string>
|
||||||
@ -762,7 +768,7 @@
|
|||||||
<string name="auth_button">Authorize</string>
|
<string name="auth_button">Authorize</string>
|
||||||
<string name="auth_success">Authorization completed successfully</string>
|
<string name="auth_success">Authorization completed successfully</string>
|
||||||
<string name="auth_title">Authorization</string>
|
<string name="auth_title">Authorization</string>
|
||||||
<string name="auth_description">To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below</string>
|
<string name="auth_description">Dear Parent,<br/><br/>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br/><br/>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br/><br/>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br/><br/>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.</string>
|
||||||
<string name="auth_button_skip">Skip for now</string>
|
<string name="auth_button_skip">Skip for now</string>
|
||||||
<!--Captcha-->
|
<!--Captcha-->
|
||||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||||
|
@ -113,13 +113,17 @@
|
|||||||
<string name="grade_comment">Komentarz</string>
|
<string name="grade_comment">Komentarz</string>
|
||||||
<string name="grade_number_new_items">Ilość nowych ocen: %1$d</string>
|
<string name="grade_number_new_items">Ilość nowych ocen: %1$d</string>
|
||||||
<string name="grade_average">Średnia: %1$.2f</string>
|
<string name="grade_average">Średnia: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Roczna: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Punkty: %s</string>
|
<string name="grade_points_sum">Punkty: %s</string>
|
||||||
<string name="grade_no_average">Brak średniej</string>
|
<string name="grade_no_average">Brak średniej</string>
|
||||||
|
<string name="grade_summary_average_semester">Średnia semestralna</string>
|
||||||
|
<string name="grade_summary_average_year">Średnia roczna</string>
|
||||||
<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_descriptive">Ocena opisowa</string>
|
||||||
<string name="grade_summary_calculated_average">Obliczona średnia</string>
|
<string name="grade_summary_calculated_average">Obliczona średnia semestralna</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Obliczona średnia roczna</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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">Jak działa końcowa średnia?</string>
|
<string name="grade_summary_final_average_help_dialog_title">Jak działa końcowa średnia?</string>
|
||||||
@ -270,6 +274,7 @@
|
|||||||
<string name="attendance_calculator_summary_balance_neutral">dokładnie u celu</string>
|
<string name="attendance_calculator_summary_balance_neutral">dokładnie u celu</string>
|
||||||
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> poniżej celu</string>
|
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> poniżej celu</string>
|
||||||
<string name="attendance_calculator_summary_values">%1$d/%2$d obecności</string>
|
<string name="attendance_calculator_summary_values">%1$d/%2$d obecności</string>
|
||||||
|
<string name="attendance_calculator_summary_values_empty">Nie odnotowano żadnej frekwencji</string>
|
||||||
<string name="attendance_absence_school">Nieobecność z przyczyn szkolnych</string>
|
<string name="attendance_absence_school">Nieobecność z przyczyn szkolnych</string>
|
||||||
<string name="attendance_absence_excused">Nieobecność usprawiedliwiona</string>
|
<string name="attendance_absence_excused">Nieobecność usprawiedliwiona</string>
|
||||||
<string name="attendance_absence_unexcused">Nieobecność nieusprawiedliwiona</string>
|
<string name="attendance_absence_unexcused">Nieobecność nieusprawiedliwiona</string>
|
||||||
@ -737,6 +742,7 @@
|
|||||||
<string name="pref_view_grade_average_force_calc">Wymuś obliczanie średniej przez aplikację</string>
|
<string name="pref_view_grade_average_force_calc">Wymuś obliczanie średniej przez aplikację</string>
|
||||||
<string name="pref_view_present">Pokazuj obecność</string>
|
<string name="pref_view_present">Pokazuj obecność</string>
|
||||||
<string name="pref_attendance_target">Docelowa obecność</string>
|
<string name="pref_attendance_target">Docelowa obecność</string>
|
||||||
|
<string name="pref_attendance_calculator_show_empty_subjects">Pokazuj przedmioty bez frekwencji</string>
|
||||||
<string name="pref_view_attendance_calculator_sorting_mode">Sortowanie kalkulatora obecności</string>
|
<string name="pref_view_attendance_calculator_sorting_mode">Sortowanie kalkulatora obecności</string>
|
||||||
<string name="pref_view_app_theme">Motyw</string>
|
<string name="pref_view_app_theme">Motyw</string>
|
||||||
<string name="pref_view_expand_grade">Rozwijanie ocen</string>
|
<string name="pref_view_expand_grade">Rozwijanie ocen</string>
|
||||||
@ -856,7 +862,7 @@
|
|||||||
<string name="auth_button">Potwierdź</string>
|
<string name="auth_button">Potwierdź</string>
|
||||||
<string name="auth_success">Autoryzacja zakończona pomyślnie</string>
|
<string name="auth_success">Autoryzacja zakończona pomyślnie</string>
|
||||||
<string name="auth_title">Autoryzacja</string>
|
<string name="auth_title">Autoryzacja</string>
|
||||||
<string name="auth_description">Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej</string>
|
<string name="auth_description">Szanowny Rodzicu,<br/><br/>W celu autoryzacji i zapewnienia bezpieczeństwa danych, uprzejmie prosimy o wprowadzenie poniżej numeru PESEL ucznia <b>%1$s</b>. Te informacje są niezbędne do prawidłowego przypisania dostępu i ochrony danych osobowych zgodnie z obowiązującymi przepisami.<br/><br/>Po wprowadzeniu danych, będą one weryfikowane w celu zapewnienia, że dostęp do systemu VULCAN jest przyznawany wyłącznie upoważnionym osobom. W przypadku jakichkolwiek wątpliwości lub problemów, prosimy o kontakt z administratorem dziennika szkolnego w celu wyjaśnienia sytuacji.<br/><br/>Zachowujemy najwyższe standardy ochrony danych osobowych i zapewniamy, że wszelkie przekazane informacje są chronione. Wulkanowy nie przechowuje ani nie przetwarza numeru PESEL.<br/><br/>Przypominamy, że podanie pełnych i prawdziwych danych jest obowiązkowe i konieczne do korzystania z systemu VULCAN.</string>
|
||||||
<string name="auth_button_skip">Na razie pomiń</string>
|
<string name="auth_button_skip">Na razie pomiń</string>
|
||||||
<!--Captcha-->
|
<!--Captcha-->
|
||||||
<string name="captcha_dialog_title">Strona dziennika VULCAN wymaga weryfikacji</string>
|
<string name="captcha_dialog_title">Strona dziennika VULCAN wymaga weryfikacji</string>
|
||||||
|
@ -113,13 +113,17 @@
|
|||||||
<string name="grade_comment">Комментарий</string>
|
<string name="grade_comment">Комментарий</string>
|
||||||
<string name="grade_number_new_items">Количество новых оценок: %1$d</string>
|
<string name="grade_number_new_items">Количество новых оценок: %1$d</string>
|
||||||
<string name="grade_average">Средняя оценка: %1$.2f</string>
|
<string name="grade_average">Средняя оценка: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Annual: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Баллы: %s</string>
|
<string name="grade_points_sum">Баллы: %s</string>
|
||||||
<string name="grade_no_average">Нет средней оценки</string>
|
<string name="grade_no_average">Нет средней оценки</string>
|
||||||
|
<string name="grade_summary_average_semester">Semester average</string>
|
||||||
|
<string name="grade_summary_average_year">Annual average</string>
|
||||||
<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_descriptive">Descriptive grade</string>
|
||||||
<string name="grade_summary_calculated_average">Рассчитанная средняя оценка</string>
|
<string name="grade_summary_calculated_average">Calculated semester average</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Calculated annual 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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">Как работает \"Итоговая средняя оценка\"?</string>
|
<string name="grade_summary_final_average_help_dialog_title">Как работает \"Итоговая средняя оценка\"?</string>
|
||||||
@ -270,6 +274,7 @@
|
|||||||
<string name="attendance_calculator_summary_balance_neutral">right on target</string>
|
<string name="attendance_calculator_summary_balance_neutral">right on target</string>
|
||||||
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> under target</string>
|
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> under target</string>
|
||||||
<string name="attendance_calculator_summary_values">%1$d/%2$d presences</string>
|
<string name="attendance_calculator_summary_values">%1$d/%2$d presences</string>
|
||||||
|
<string name="attendance_calculator_summary_values_empty">No attendances recorded</string>
|
||||||
<string name="attendance_absence_school">Отсутствие по школьным причинам</string>
|
<string name="attendance_absence_school">Отсутствие по школьным причинам</string>
|
||||||
<string name="attendance_absence_excused">Отсутствие по уважительной причине</string>
|
<string name="attendance_absence_excused">Отсутствие по уважительной причине</string>
|
||||||
<string name="attendance_absence_unexcused">Отсутствие по неуважительной причине</string>
|
<string name="attendance_absence_unexcused">Отсутствие по неуважительной причине</string>
|
||||||
@ -737,6 +742,7 @@
|
|||||||
<string name="pref_view_grade_average_force_calc">Принудительно высчитать среднюю оценку через приложение</string>
|
<string name="pref_view_grade_average_force_calc">Принудительно высчитать среднюю оценку через приложение</string>
|
||||||
<string name="pref_view_present">Показывать присутствия</string>
|
<string name="pref_view_present">Показывать присутствия</string>
|
||||||
<string name="pref_attendance_target">Attendance target</string>
|
<string name="pref_attendance_target">Attendance target</string>
|
||||||
|
<string name="pref_attendance_calculator_show_empty_subjects">Show subjects without any attendances</string>
|
||||||
<string name="pref_view_attendance_calculator_sorting_mode">Attendance calculator sorting</string>
|
<string name="pref_view_attendance_calculator_sorting_mode">Attendance calculator sorting</string>
|
||||||
<string name="pref_view_app_theme">Тема</string>
|
<string name="pref_view_app_theme">Тема</string>
|
||||||
<string name="pref_view_expand_grade">Разворачивание оценок</string>
|
<string name="pref_view_expand_grade">Разворачивание оценок</string>
|
||||||
@ -856,7 +862,7 @@
|
|||||||
<string name="auth_button">Авторизовать</string>
|
<string name="auth_button">Авторизовать</string>
|
||||||
<string name="auth_success">Авторизация прошла успешно</string>
|
<string name="auth_success">Авторизация прошла успешно</string>
|
||||||
<string name="auth_title">Авторизация</string>
|
<string name="auth_title">Авторизация</string>
|
||||||
<string name="auth_description">Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже</string>
|
<string name="auth_description">Dear Parent,<br/><br/>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br/><br/>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br/><br/>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br/><br/>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.</string>
|
||||||
<string name="auth_button_skip">Пропустить сейчас</string>
|
<string name="auth_button_skip">Пропустить сейчас</string>
|
||||||
<!--Captcha-->
|
<!--Captcha-->
|
||||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<!--Subtitles-->
|
<!--Subtitles-->
|
||||||
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
|
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
|
||||||
<!--Login-->
|
<!--Login-->
|
||||||
<string name="login_header_default">Prihláste sa pomocou študentského alebo rodičovského konta</string>
|
<string name="login_header_default">Prihláste sa pomocou žiackeho alebo rodičovského účtu</string>
|
||||||
<string name="login_header_symbol">Zadajte symbol zo stránky denníka: <b>%1$s</b></string>
|
<string name="login_header_symbol">Zadajte symbol zo stránky denníka: <b>%1$s</b></string>
|
||||||
<string name="login_nickname_hint">Užívateľské meno</string>
|
<string name="login_nickname_hint">Užívateľské meno</string>
|
||||||
<string name="login_email_hint">Email</string>
|
<string name="login_email_hint">Email</string>
|
||||||
@ -113,13 +113,17 @@
|
|||||||
<string name="grade_comment">Komentár</string>
|
<string name="grade_comment">Komentár</string>
|
||||||
<string name="grade_number_new_items">Počet nových známok %1$d</string>
|
<string name="grade_number_new_items">Počet nových známok %1$d</string>
|
||||||
<string name="grade_average">Priemer: %1$.2f</string>
|
<string name="grade_average">Priemer: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Annual: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Body: %s</string>
|
<string name="grade_points_sum">Body: %s</string>
|
||||||
<string name="grade_no_average">Bez priemeru</string>
|
<string name="grade_no_average">Bez priemeru</string>
|
||||||
|
<string name="grade_summary_average_semester">Semester average</string>
|
||||||
|
<string name="grade_summary_average_year">Annual average</string>
|
||||||
<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_descriptive">Popisná známka</string>
|
||||||
<string name="grade_summary_calculated_average">Vypočítaný priemer</string>
|
<string name="grade_summary_calculated_average">Calculated semester average</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Calculated annual average</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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">Ako funguje konečný priemer?</string>
|
<string name="grade_summary_final_average_help_dialog_title">Ako funguje konečný priemer?</string>
|
||||||
@ -270,6 +274,7 @@
|
|||||||
<string name="attendance_calculator_summary_balance_neutral">presne v cieli</string>
|
<string name="attendance_calculator_summary_balance_neutral">presne v cieli</string>
|
||||||
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> pod cieľom</string>
|
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> pod cieľom</string>
|
||||||
<string name="attendance_calculator_summary_values">%1$d/%2$d prítomnosti</string>
|
<string name="attendance_calculator_summary_values">%1$d/%2$d prítomnosti</string>
|
||||||
|
<string name="attendance_calculator_summary_values_empty">Nebola zaznamenaná žiadna dochádzka</string>
|
||||||
<string name="attendance_absence_school">Neprítomnosť zo školských dôvodov</string>
|
<string name="attendance_absence_school">Neprítomnosť zo školských dôvodov</string>
|
||||||
<string name="attendance_absence_excused">Ospravedlnená neprítomnosť</string>
|
<string name="attendance_absence_excused">Ospravedlnená neprítomnosť</string>
|
||||||
<string name="attendance_absence_unexcused">Neospravedlnená neprítomnosť</string>
|
<string name="attendance_absence_unexcused">Neospravedlnená neprítomnosť</string>
|
||||||
@ -737,6 +742,7 @@
|
|||||||
<string name="pref_view_grade_average_force_calc">Vynútiť priemerný výpočet podľa aplikácie</string>
|
<string name="pref_view_grade_average_force_calc">Vynútiť priemerný výpočet podľa aplikácie</string>
|
||||||
<string name="pref_view_present">Zobraziť prítomnosť</string>
|
<string name="pref_view_present">Zobraziť prítomnosť</string>
|
||||||
<string name="pref_attendance_target">Cieľová dochádzka</string>
|
<string name="pref_attendance_target">Cieľová dochádzka</string>
|
||||||
|
<string name="pref_attendance_calculator_show_empty_subjects">Zobraziť predmety bez dochádzok</string>
|
||||||
<string name="pref_view_attendance_calculator_sorting_mode">Triedenie kalkulačky dochádzky</string>
|
<string name="pref_view_attendance_calculator_sorting_mode">Triedenie kalkulačky dochádzky</string>
|
||||||
<string name="pref_view_app_theme">Motív</string>
|
<string name="pref_view_app_theme">Motív</string>
|
||||||
<string name="pref_view_expand_grade">Rozvijanie známok</string>
|
<string name="pref_view_expand_grade">Rozvijanie známok</string>
|
||||||
@ -856,7 +862,7 @@
|
|||||||
<string name="auth_button">Autorizovať</string>
|
<string name="auth_button">Autorizovať</string>
|
||||||
<string name="auth_success">Autorizácia bola úspešne dokončená</string>
|
<string name="auth_success">Autorizácia bola úspešne dokončená</string>
|
||||||
<string name="auth_title">Autorizácia</string>
|
<string name="auth_title">Autorizácia</string>
|
||||||
<string name="auth_description">Na prevádzku aplikácie potrebujeme potvrdiť vašu identitu. Zadajte PESEL žiaka <b>%1$s</b> v nižšie uvedenom poli</string>
|
<string name="auth_description">Dear Parent,<br/><br/>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br/><br/>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br/><br/>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br/><br/>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.</string>
|
||||||
<string name="auth_button_skip">Zatiaľ preskočiť</string>
|
<string name="auth_button_skip">Zatiaľ preskočiť</string>
|
||||||
<!--Captcha-->
|
<!--Captcha-->
|
||||||
<string name="captcha_dialog_title">Webová stránka denníka VULCAN vyžaduje overenie</string>
|
<string name="captcha_dialog_title">Webová stránka denníka VULCAN vyžaduje overenie</string>
|
||||||
|
@ -113,13 +113,17 @@
|
|||||||
<string name="grade_comment">Коментар</string>
|
<string name="grade_comment">Коментар</string>
|
||||||
<string name="grade_number_new_items">Кількість нових оцінок: %1$d</string>
|
<string name="grade_number_new_items">Кількість нових оцінок: %1$d</string>
|
||||||
<string name="grade_average">Середня оцінка: %1$.2f</string>
|
<string name="grade_average">Середня оцінка: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Підсумкова: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Бали: %s</string>
|
<string name="grade_points_sum">Бали: %s</string>
|
||||||
<string name="grade_no_average">Середня оцінка відсутня</string>
|
<string name="grade_no_average">Середня оцінка відсутня</string>
|
||||||
|
<string name="grade_summary_average_semester">Середня за семестр</string>
|
||||||
|
<string name="grade_summary_average_year">Підсумкова середня оцінка</string>
|
||||||
<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_descriptive">Описова оцінка</string>
|
||||||
<string name="grade_summary_calculated_average">Розрахована середня оцінка</string>
|
<string name="grade_summary_calculated_average">Розрахована середня за семестр</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Розрахована підсумкова середня оцінка</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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">Як працює \"Підсумкова середня оцінка\"?</string>
|
<string name="grade_summary_final_average_help_dialog_title">Як працює \"Підсумкова середня оцінка\"?</string>
|
||||||
@ -270,6 +274,7 @@
|
|||||||
<string name="attendance_calculator_summary_balance_neutral">точно у цілі</string>
|
<string name="attendance_calculator_summary_balance_neutral">точно у цілі</string>
|
||||||
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> під ціллю</string>
|
<string name="attendance_calculator_summary_balance_negative"><b>%1$d</b> під ціллю</string>
|
||||||
<string name="attendance_calculator_summary_values">%1$d/%2$d відвідуваності</string>
|
<string name="attendance_calculator_summary_values">%1$d/%2$d відвідуваності</string>
|
||||||
|
<string name="attendance_calculator_summary_values_empty">Немає жодних записаних відвідувань</string>
|
||||||
<string name="attendance_absence_school">Відсутність зі шкільних причин</string>
|
<string name="attendance_absence_school">Відсутність зі шкільних причин</string>
|
||||||
<string name="attendance_absence_excused">Відсутність з поважних причин</string>
|
<string name="attendance_absence_excused">Відсутність з поважних причин</string>
|
||||||
<string name="attendance_absence_unexcused">Відсутність без поважних причин</string>
|
<string name="attendance_absence_unexcused">Відсутність без поважних причин</string>
|
||||||
@ -737,6 +742,7 @@
|
|||||||
<string name="pref_view_grade_average_force_calc">Примусово розраховувати середню оцінку через додаток</string>
|
<string name="pref_view_grade_average_force_calc">Примусово розраховувати середню оцінку через додаток</string>
|
||||||
<string name="pref_view_present">Показувати присутність</string>
|
<string name="pref_view_present">Показувати присутність</string>
|
||||||
<string name="pref_attendance_target">Цільова відвідуваність</string>
|
<string name="pref_attendance_target">Цільова відвідуваність</string>
|
||||||
|
<string name="pref_attendance_calculator_show_empty_subjects">Показувати уроки без відвідувань</string>
|
||||||
<string name="pref_view_attendance_calculator_sorting_mode">Сортування калькулятора відвідування</string>
|
<string name="pref_view_attendance_calculator_sorting_mode">Сортування калькулятора відвідування</string>
|
||||||
<string name="pref_view_app_theme">Тема</string>
|
<string name="pref_view_app_theme">Тема</string>
|
||||||
<string name="pref_view_expand_grade">Розгортання оцінок</string>
|
<string name="pref_view_expand_grade">Розгортання оцінок</string>
|
||||||
@ -856,7 +862,7 @@
|
|||||||
<string name="auth_button">Авторизовать</string>
|
<string name="auth_button">Авторизовать</string>
|
||||||
<string name="auth_success">Авторизація пройшла успішно</string>
|
<string name="auth_success">Авторизація пройшла успішно</string>
|
||||||
<string name="auth_title">Авторизувати</string>
|
<string name="auth_title">Авторизувати</string>
|
||||||
<string name="auth_description">Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче</string>
|
<string name="auth_description">Шановні батьки,<br/><br/>Для авторизації та забезпечення безпеки даних просимо Вас ввести нижче PESEL номер учня <b>%1$s</b>. Ці дані необхідні для правильного призначення доступу та захисту персональних даних відповідно до чинного законодавства.<br/><br/>Після введення даних буде проведена перевірка, щоб переконатися, що доступ до системи VULCAN надається виключно уповноваженим особам. У разі виникнення будь-яких сумнівів або проблем, будь ласка, зв\'яжіться з адміністратором шкільного щоденника для з\'ясування ситуації.<br/><br/>Ми підтримуємо найвищі стандарти захисту персональних даних і гарантуємо, що вся надана інформація є безпечною. Додаток Wulkanowy не зберігає і не обробляє номер PESEL.<br/><br/>Нагадуємо, що надання повних і точних даних є обов\'язковим і необхідним для використання системи VULCAN.</string>
|
||||||
<string name="auth_button_skip">Поки що пропустити</string>
|
<string name="auth_button_skip">Поки що пропустити</string>
|
||||||
<!--Captcha-->
|
<!--Captcha-->
|
||||||
<string name="captcha_dialog_title">Веб-сайт VULCAN потребує підтвердження</string>
|
<string name="captcha_dialog_title">Веб-сайт VULCAN потребує підтвердження</string>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
<string name="pref_key_start_menu">default_menu_index</string>
|
<string name="pref_key_start_menu">default_menu_index</string>
|
||||||
|
<string name="pref_key_attendance_calculator">attendance_calculator</string>
|
||||||
<string name="pref_key_attendance_present">attendance_present</string>
|
<string name="pref_key_attendance_present">attendance_present</string>
|
||||||
<string name="pref_key_attendance_target">attendance_target</string>
|
<string name="pref_key_attendance_target">attendance_target</string>
|
||||||
<string name="pref_key_attendance_calculator_sorting_mode">attendance_calculator_sorting_mode</string>
|
<string name="pref_key_attendance_calculator_sorting_mode">attendance_calculator_sorting_mode</string>
|
||||||
|
@ -126,13 +126,17 @@
|
|||||||
<string name="grade_comment">Comment</string>
|
<string name="grade_comment">Comment</string>
|
||||||
<string name="grade_number_new_items">Number of new ratings: %1$d</string>
|
<string name="grade_number_new_items">Number of new ratings: %1$d</string>
|
||||||
<string name="grade_average">Average: %1$.2f</string>
|
<string name="grade_average">Average: %1$.2f</string>
|
||||||
|
<string name="grade_average_year">Annual: %1$.2f</string>
|
||||||
<string name="grade_points_sum">Points: %s</string>
|
<string name="grade_points_sum">Points: %s</string>
|
||||||
<string name="grade_no_average">No average</string>
|
<string name="grade_no_average">No average</string>
|
||||||
|
<string name="grade_summary_average_semester">Semester average</string>
|
||||||
|
<string name="grade_summary_average_year">Annual average</string>
|
||||||
<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_descriptive">Descriptive grade</string>
|
||||||
<string name="grade_summary_calculated_average">Calculated average</string>
|
<string name="grade_summary_calculated_average">Calculated semester average</string>
|
||||||
|
<string name="grade_summary_calculated_average_annual">Calculated annual 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>
|
||||||
<string name="grade_summary_final_average_help_dialog_title">How does the Final Average work?</string>
|
<string name="grade_summary_final_average_help_dialog_title">How does the Final Average work?</string>
|
||||||
@ -792,6 +796,8 @@
|
|||||||
<string name="pref_dashboard_appearance_header">Dashboard</string>
|
<string name="pref_dashboard_appearance_header">Dashboard</string>
|
||||||
<string name="pref_dashboard_appearance_tiles_title">Tiles visibility</string>
|
<string name="pref_dashboard_appearance_tiles_title">Tiles visibility</string>
|
||||||
<string name="pref_attendance_appearance_view">Attendance</string>
|
<string name="pref_attendance_appearance_view">Attendance</string>
|
||||||
|
<string name="pref_attendance_calculator_appearance_view">Attendance calculator</string>
|
||||||
|
<string name="pref_attendance_calculator_appearance_settings_title">Settings</string>
|
||||||
<string name="pref_timetable_appearance_view">Timetable</string>
|
<string name="pref_timetable_appearance_view">Timetable</string>
|
||||||
<string name="pref_grades_advanced_header">Grades</string>
|
<string name="pref_grades_advanced_header">Grades</string>
|
||||||
<string name="pref_counted_average_advanced_header">Calculated average</string>
|
<string name="pref_counted_average_advanced_header">Calculated average</string>
|
||||||
@ -852,7 +858,7 @@
|
|||||||
<string name="auth_button">Authorize</string>
|
<string name="auth_button">Authorize</string>
|
||||||
<string name="auth_success">Authorization completed successfully</string>
|
<string name="auth_success">Authorization completed successfully</string>
|
||||||
<string name="auth_title">Authorization</string>
|
<string name="auth_title">Authorization</string>
|
||||||
<string name="auth_description">To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below</string>
|
<string name="auth_description">Dear Parent,<br /><br />To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br /><br />After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br /><br />We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br /><br />We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.</string>
|
||||||
<string name="auth_button_skip">Skip for now</string>
|
<string name="auth_button_skip">Skip for now</string>
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,6 +85,11 @@
|
|||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:key="@string/pref_key_attendance_present"
|
app:key="@string/pref_key_attendance_present"
|
||||||
app:title="@string/pref_view_present" />
|
app:title="@string/pref_view_present" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:title="@string/pref_attendance_calculator_appearance_view"
|
||||||
|
app:key="@string/pref_key_attendance_calculator">
|
||||||
<SeekBarPreference
|
<SeekBarPreference
|
||||||
app:defaultValue="@integer/pref_default_attendance_target"
|
app:defaultValue="@integer/pref_default_attendance_target"
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
|
11
app/src/play/AndroidManifest.xml
Normal file
11
app/src/play/AndroidManifest.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<application tools:ignore="MissingApplicationIcon">
|
||||||
|
<property
|
||||||
|
android:name="android.adservices.AD_SERVICES_CONFIG"
|
||||||
|
android:resource="@xml/gma_ad_services_config"
|
||||||
|
tools:replace="android:resource" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -93,9 +93,13 @@ class AdsHelper @Inject constructor(
|
|||||||
private fun initializeMobileAds() {
|
private fun initializeMobileAds() {
|
||||||
if (isMobileAdsInitializeCalled.getAndSet(true)) return
|
if (isMobileAdsInitializeCalled.getAndSet(true)) return
|
||||||
|
|
||||||
|
try {
|
||||||
MobileAds.initialize(context) {
|
MobileAds.initialize(context) {
|
||||||
isMobileAdsSdkInitialized.value = true
|
isMobileAdsSdkInitialized.value = true
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSupportAd(): RewardedInterstitialAd? {
|
suspend fun getSupportAd(): RewardedInterstitialAd? {
|
||||||
|
@ -2,11 +2,12 @@ package io.github.wulkanowy
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.WulkanowySdkFactory
|
import io.github.wulkanowy.data.WulkanowySdkFactory
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|
||||||
fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>()
|
fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>()
|
||||||
.apply {
|
.apply {
|
||||||
every { create() } returns sdk
|
every { create() } returns sdk
|
||||||
every { create(any(), any()) } answers { callOriginal() }
|
coEvery { create(any(), any()) } returns sdk
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package io.github.wulkanowy.data
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import dagger.hilt.android.testing.HiltTestApplication
|
||||||
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
|
||||||
|
import io.github.wulkanowy.getStudentEntity
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.sdk.pojo.RegisterStudent
|
||||||
|
import io.mockk.Runs
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
|
||||||
|
class WulkanowySdkFactoryTest {
|
||||||
|
|
||||||
|
private lateinit var wulkanowySdkFactory: WulkanowySdkFactory
|
||||||
|
private lateinit var studentDao: StudentDao
|
||||||
|
private lateinit var sdk: Sdk
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
sdk = mockk(relaxed = true)
|
||||||
|
studentDao = mockk()
|
||||||
|
wulkanowySdkFactory = spyk(
|
||||||
|
WulkanowySdkFactory(
|
||||||
|
chuckerInterceptor = mockk(),
|
||||||
|
remoteConfig = mockk(relaxed = true),
|
||||||
|
webkitCookieManagerProxy = mockk(),
|
||||||
|
studentDb = studentDao
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
every { wulkanowySdkFactory.create() } returns sdk
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check sdk flag isEduOne when local student is eduone`() = runTest {
|
||||||
|
val student = getStudentEntity().copy(isEduOne = true)
|
||||||
|
|
||||||
|
wulkanowySdkFactory.create(student)
|
||||||
|
|
||||||
|
verify { sdk.isEduOne = true }
|
||||||
|
coVerify(exactly = 0) { sdk.getCurrentStudent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check sdk flag isEduOne when local student is not eduone`() = runTest {
|
||||||
|
val student = getStudentEntity().copy(isEduOne = false)
|
||||||
|
|
||||||
|
wulkanowySdkFactory.create(student)
|
||||||
|
|
||||||
|
verify { sdk.isEduOne = false }
|
||||||
|
coVerify(exactly = 0) { sdk.getCurrentStudent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check sdk flag isEduOne when local student is eduone null and remote student is eduone true`() =
|
||||||
|
runTest {
|
||||||
|
val studentToProcess = getStudentEntity().copy(isEduOne = null)
|
||||||
|
val registerStudent = studentToProcess.toRegisterStudent(isEduOne = true)
|
||||||
|
|
||||||
|
coEvery { studentDao.loadById(any()) } returns studentToProcess
|
||||||
|
coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs
|
||||||
|
coEvery { sdk.getCurrentStudent() } returns registerStudent
|
||||||
|
|
||||||
|
wulkanowySdkFactory.create(studentToProcess)
|
||||||
|
|
||||||
|
verify { sdk.isEduOne = true }
|
||||||
|
coVerify { sdk.getCurrentStudent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check sdk flag isEduOne when local student is eduone null and remote student is eduone false`() =
|
||||||
|
runTest {
|
||||||
|
val studentToProcess = getStudentEntity().copy(isEduOne = null)
|
||||||
|
val registerStudent = studentToProcess.toRegisterStudent(isEduOne = false)
|
||||||
|
|
||||||
|
coEvery { studentDao.loadById(any()) } returns studentToProcess
|
||||||
|
coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs
|
||||||
|
coEvery { sdk.getCurrentStudent() } returns registerStudent
|
||||||
|
|
||||||
|
wulkanowySdkFactory.create(studentToProcess)
|
||||||
|
|
||||||
|
verify { sdk.isEduOne = false }
|
||||||
|
coVerify { sdk.getCurrentStudent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check sdk flag isEduOne when sdk getCurrentStudent throws error`() =
|
||||||
|
runTest {
|
||||||
|
val studentToProcess = getStudentEntity().copy(isEduOne = null)
|
||||||
|
|
||||||
|
coEvery { studentDao.loadById(any()) } returns studentToProcess
|
||||||
|
coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs
|
||||||
|
coEvery { sdk.getCurrentStudent() } throws Exception()
|
||||||
|
|
||||||
|
wulkanowySdkFactory.create(studentToProcess)
|
||||||
|
|
||||||
|
verify { sdk.isEduOne = false }
|
||||||
|
coVerify { sdk.getCurrentStudent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Student.toRegisterStudent(isEduOne: Boolean) = RegisterStudent(
|
||||||
|
studentId = studentId,
|
||||||
|
studentName = studentName,
|
||||||
|
studentSecondName = studentName,
|
||||||
|
studentSurname = studentName,
|
||||||
|
className = className,
|
||||||
|
classId = classId,
|
||||||
|
isParent = isParent,
|
||||||
|
isAuthorized = isAuthorized,
|
||||||
|
semesters = emptyList(),
|
||||||
|
isEduOne = isEduOne,
|
||||||
|
)
|
||||||
|
}
|
@ -21,10 +21,10 @@ abstract class AbstractMigrationTest {
|
|||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val helper: MigrationTestHelper = MigrationTestHelper(
|
val helper: MigrationTestHelper = MigrationTestHelper(
|
||||||
InstrumentationRegistry.getInstrumentation(),
|
instrumentation = InstrumentationRegistry.getInstrumentation(),
|
||||||
AppDatabase::class.java,
|
databaseClass = AppDatabase::class.java,
|
||||||
listOf(Migration55()),
|
specs = listOf(Migration63()),
|
||||||
FrameworkSQLiteOpenHelperFactory()
|
openFactory = FrameworkSQLiteOpenHelperFactory()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun runMigrationsAndValidate(migration: Migration) {
|
fun runMigrationsAndValidate(migration: Migration) {
|
||||||
|
@ -7,8 +7,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
|||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import dagger.hilt.android.testing.HiltTestApplication
|
import dagger.hilt.android.testing.HiltTestApplication
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.*
|
import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.ADFSLight
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.ADFSLightScoped
|
||||||
|
import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.STANDARD
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@ -19,7 +20,6 @@ import kotlin.test.assertEquals
|
|||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
|
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
|
||||||
class Migration54Test : AbstractMigrationTest() {
|
class Migration54Test : AbstractMigrationTest() {
|
||||||
|
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import dagger.hilt.android.testing.HiltTestApplication
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
|
||||||
|
class Migration63Test : AbstractMigrationTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `update is_edu_one to null if 0`() = runTest {
|
||||||
|
with(helper.createDatabase(dbName, 62)) {
|
||||||
|
createStudent(1, 0)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.runMigrationsAndValidate(dbName, 63, true)
|
||||||
|
|
||||||
|
val database = getMigratedRoomDatabase()
|
||||||
|
val studentDb = database.studentDao
|
||||||
|
val student = studentDb.loadById(1)
|
||||||
|
|
||||||
|
assertNull(student!!.isEduOne)
|
||||||
|
|
||||||
|
database.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check is_edu_one is stay same`() = runTest {
|
||||||
|
with(helper.createDatabase(dbName, 62)) {
|
||||||
|
createStudent(1, 1)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.runMigrationsAndValidate(dbName, 63, true)
|
||||||
|
|
||||||
|
val database = getMigratedRoomDatabase()
|
||||||
|
val studentDb = database.studentDao
|
||||||
|
val student = studentDb.loadById(1)
|
||||||
|
|
||||||
|
val isEduOne = assertNotNull(student!!.isEduOne)
|
||||||
|
assertTrue(isEduOne)
|
||||||
|
database.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) {
|
||||||
|
insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
|
||||||
|
put("scrapper_base_url", "https://fakelog.cf")
|
||||||
|
put("mobile_base_url", "")
|
||||||
|
put("login_type", "SCRAPPER")
|
||||||
|
put("login_mode", "SCRAPPER")
|
||||||
|
put("certificate_key", "")
|
||||||
|
put("private_key", "")
|
||||||
|
put("is_parent", false)
|
||||||
|
put("email", "jan@fakelog.cf")
|
||||||
|
put("password", "******")
|
||||||
|
put("symbol", "symbol")
|
||||||
|
put("student_id", Random.nextInt())
|
||||||
|
put("user_login_id", 123)
|
||||||
|
put("user_name", "studentName")
|
||||||
|
put("student_name", "studentName")
|
||||||
|
put("school_id", "123")
|
||||||
|
put("school_short", "")
|
||||||
|
put("school_name", "")
|
||||||
|
put("class_name", "")
|
||||||
|
put("class_id", Random.nextInt())
|
||||||
|
put("is_current", false)
|
||||||
|
put("registration_date", "0")
|
||||||
|
put("id", id)
|
||||||
|
put("nick", "")
|
||||||
|
put("avatar_color", "")
|
||||||
|
put("is_edu_one", isEduOneValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -48,8 +48,11 @@ class LuckyNumberRemoteTest {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
|
|
||||||
luckyNumberRepository =
|
luckyNumberRepository = LuckyNumberRepository(
|
||||||
LuckyNumberRepository(luckyNumberDb, wulkanowySdkFactory, appWidgetUpdater)
|
luckyNumberDb = luckyNumberDb,
|
||||||
|
wulkanowySdkFactory = wulkanowySdkFactory,
|
||||||
|
appWidgetUpdater = appWidgetUpdater,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1679,7 +1679,9 @@ class GradeAverageProviderTest {
|
|||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
finalGrade = "",
|
finalGrade = "",
|
||||||
predictedGrade = "",
|
predictedGrade = "",
|
||||||
position = 0
|
position = 0,
|
||||||
|
pointsSumAllYear = null,
|
||||||
|
averageAllYear = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,13 +23,15 @@ class GradeExtensionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun calcWeightedAverage() {
|
fun calcWeightedAverage() {
|
||||||
assertEquals(3.47, listOf(
|
assertEquals(
|
||||||
|
3.47, listOf(
|
||||||
createGrade(5.0, 6.0, 0.33),
|
createGrade(5.0, 6.0, 0.33),
|
||||||
createGrade(5.0, 5.0, -0.33),
|
createGrade(5.0, 5.0, -0.33),
|
||||||
createGrade(4.0, 1.0, 0.0),
|
createGrade(4.0, 1.0, 0.0),
|
||||||
createGrade(1.0, 9.0, 0.5),
|
createGrade(1.0, 9.0, 0.5),
|
||||||
createGrade(0.0, .0, 0.0)
|
createGrade(0.0, .0, 0.0)
|
||||||
).calcAverage(false), 0.005)
|
).calcAverage(false), 0.005
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -86,7 +88,11 @@ class GradeExtensionTest {
|
|||||||
assertEquals(-.25, createGrade(5.0, .0, -.33).changeModifier(.0, .25).modifier, .0)
|
assertEquals(-.25, createGrade(5.0, .0, -.33).changeModifier(.0, .25).modifier, .0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createGrade(value: Double, weightValue: Double = .0, modifier: Double = 0.25): Grade {
|
private fun createGrade(
|
||||||
|
value: Double,
|
||||||
|
weightValue: Double = .0,
|
||||||
|
modifier: Double = 0.25
|
||||||
|
): Grade {
|
||||||
return Grade(
|
return Grade(
|
||||||
semesterId = 1,
|
semesterId = 1,
|
||||||
studentId = 1,
|
studentId = 1,
|
||||||
@ -116,7 +122,9 @@ class GradeExtensionTest {
|
|||||||
proposedPoints = "",
|
proposedPoints = "",
|
||||||
finalPoints = "",
|
finalPoints = "",
|
||||||
pointsSum = "",
|
pointsSum = "",
|
||||||
average = .0
|
average = .0,
|
||||||
|
pointsSumAllYear = null,
|
||||||
|
averageAllYear = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
kotlin_version = '1.9.23'
|
kotlin_version = '1.9.23'
|
||||||
about_libraries = '11.1.0'
|
about_libraries = '11.1.3'
|
||||||
hilt_version = '2.51'
|
hilt_version = '2.51.1'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -14,14 +14,14 @@ 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.19"
|
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.19"
|
||||||
classpath 'com.android.tools.build:gradle:8.2.2'
|
classpath 'com.android.tools.build:gradle:8.3.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.1'
|
classpath 'com.google.gms:google-services:4.4.1'
|
||||||
classpath 'com.huawei.agconnect:agcp:1.9.1.303'
|
classpath 'com.huawei.agconnect:agcp:1.9.1.303'
|
||||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
||||||
classpath "com.github.triplet.gradle:play-publisher:3.8.4"
|
classpath "com.github.triplet.gradle:play-publisher:3.8.4"
|
||||||
classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2"
|
classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2"
|
||||||
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373"
|
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.0.0.4638"
|
||||||
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
|
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
|
||||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
|
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user