1
0

Compare commits

..

6 Commits

74 changed files with 565 additions and 1748 deletions

1
.gitignore vendored
View File

@ -127,4 +127,3 @@ google-services.json
!app/google-services.json !app/google-services.json
.idea/appInsightsSettings.xml

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 175 versionCode 161
versionName "2.6.15" versionName "2.6.1"
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.99d userFraction = 0.25d
updatePriority = 3 updatePriority = 1
enabled.set(false) enabled.set(false)
} }
@ -186,30 +186,28 @@ ext {
android_hilt = "1.2.0" android_hilt = "1.2.0"
room = "2.6.1" room = "2.6.1"
chucker = "4.0.0" chucker = "4.0.0"
mockk = "1.13.11" mockk = "1.13.10"
coroutines = "1.8.1" coroutines = "1.8.0"
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:sdk:2.6.13' implementation 'io.github.wulkanowy:sdk:2.6.0'
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 "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines"
implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.activity:activity-ktx:1.9.0" 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.7.1" implementation "androidx.fragment:fragment-ktx:1.7.0"
implementation "androidx.annotation:annotation:1.8.0" implementation "androidx.annotation:annotation:1.7.1"
implementation "androidx.javascriptengine:javascriptengine:1.0.0-beta01"
implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.recyclerview:recyclerview:1.3.2"
implementation "androidx.viewpager2:viewpager2:1.1.0" implementation "androidx.viewpager2:viewpager2:1.1.0-rc01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@ -221,7 +219,7 @@ dependencies {
implementation "androidx.work:work-runtime:$work_manager" implementation "androidx.work:work-runtime:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -256,7 +254,7 @@ dependencies {
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.firebase:firebase-config' playImplementation 'com.google.firebase:firebase-config'
playImplementation 'com.google.android.gms:play-services-ads:23.1.0' playImplementation 'com.google.android.gms:play-services-ads:22.6.0'
playImplementation "com.google.android.play:integrity:1.3.0" playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1' playImplementation 'com.google.android.play:review-ktx:2.0.1'
@ -276,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.12.2' 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"

View File

@ -3,8 +3,6 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
<uses-sdk tools:overrideLibrary="androidx.javascriptengine" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
@ -44,9 +42,9 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:resizeableActivity="true"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
android:resizeableActivity="true"
tools:ignore="DataExtractionRules,UnusedAttribute"> tools:ignore="DataExtractionRules,UnusedAttribute">
<activity <activity
android:name=".ui.modules.splash.SplashActivity" android:name=".ui.modules.splash.SplashActivity"

View File

@ -13,8 +13,8 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import io.github.wulkanowy.data.api.services.SchoolsService import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.api.services.WulkanowyService import io.github.wulkanowy.data.api.SchoolsService
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
@ -71,7 +71,7 @@ internal class DataModule {
okHttpClient: OkHttpClient, okHttpClient: OkHttpClient,
json: Json, json: Json,
appInfo: AppInfo appInfo: AppInfo
): WulkanowyService = Retrofit.Builder() ): AdminMessageService = Retrofit.Builder()
.baseUrl(appInfo.messagesBaseUrl) .baseUrl(appInfo.messagesBaseUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))

View File

@ -1,21 +1,13 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
import android.content.Context
import android.os.Build
import androidx.javascriptengine.JavaScriptSandbox
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.google.common.util.concurrent.ListenableFuture
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.dao.StudentDao 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.data.db.entities.StudentIsEduOne
import io.github.wulkanowy.data.repositories.WulkanowyRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.scrapper.EvaluateHandler
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.guava.await
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import timber.log.Timber import timber.log.Timber
@ -24,24 +16,18 @@ import javax.inject.Singleton
@Singleton @Singleton
class WulkanowySdkFactory @Inject constructor( class WulkanowySdkFactory @Inject constructor(
@ApplicationContext private val context: Context,
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 studentDb: StudentDao,
private val wulkanowyRepository: WulkanowyRepository,
) { ) {
private val eduOneMutex = Mutex() private val eduOneMutex = Mutex()
private val migrationFailedStudentIds = mutableSetOf<Long>() private val migrationFailedStudentIds = mutableSetOf<Long>()
private val sandbox: ListenableFuture<JavaScriptSandbox>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && JavaScriptSandbox.isSupported())
JavaScriptSandbox.createConnectedInstanceAsync(context)
else null
private val sdk = Sdk().apply { private val sdk = Sdk().apply {
androidVersion = Build.VERSION.RELEASE androidVersion = android.os.Build.VERSION.RELEASE
buildTag = Build.MODEL buildTag = android.os.Build.MODEL
userAgentTemplate = remoteConfig.userAgentTemplate userAgentTemplate = remoteConfig.userAgentTemplate
setSimpleHttpLogger { Timber.d(it) } setSimpleHttpLogger { Timber.d(it) }
setAdditionalCookieManager(webkitCookieManagerProxy) setAdditionalCookieManager(webkitCookieManagerProxy)
@ -50,47 +36,14 @@ class WulkanowySdkFactory @Inject constructor(
addInterceptor(chuckerInterceptor, network = true) addInterceptor(chuckerInterceptor, network = true)
} }
fun createBase() = sdk fun create() = sdk
suspend fun create(): Sdk {
val mapping = wulkanowyRepository.getMapping()
return createBase().apply {
if (mapping != null) {
endpointsMapping = mapping.endpoints
vTokenMapping = mapping.vTokens
vHeaders = mapping.vHeaders
responseMapping = mapping.responseMap
vParamsEvaluation = createIsolate()
}
}
}
private suspend fun createIsolate(): suspend () -> EvaluateHandler {
return {
val isolate = sandbox?.await()?.createIsolate()
object : EvaluateHandler {
override suspend fun evaluate(code: String): String? {
return isolate?.evaluateJavaScriptAsync(code)?.await()
}
override fun close() {
isolate?.close()
}
}
}
}
suspend fun create(student: Student, semester: Semester? = null): Sdk { suspend fun create(student: Student, semester: Semester? = null): Sdk {
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student) val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
return buildSdk(student, semester, overrideIsEduOne) return buildSdk(student, semester, overrideIsEduOne)
} }
private suspend fun buildSdk( private fun buildSdk(student: Student, semester: Semester?, isStudentEduOne: Boolean): Sdk {
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

View File

@ -1,16 +1,12 @@
package io.github.wulkanowy.data.api.services package io.github.wulkanowy.data.api
import io.github.wulkanowy.data.api.models.Mapping
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.AdminMessage
import retrofit2.http.GET import retrofit2.http.GET
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
interface WulkanowyService { interface AdminMessageService {
@GET("/v1.json") @GET("/v1.json")
suspend fun getAdminMessages(): List<AdminMessage> suspend fun getAdminMessages(): List<AdminMessage>
}
@GET("/mapping4.json")
suspend fun getMapping(): Mapping
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.data.api.services package io.github.wulkanowy.data.api
import io.github.wulkanowy.data.pojos.IntegrityRequest import io.github.wulkanowy.data.pojos.IntegrityRequest
import io.github.wulkanowy.data.pojos.LoginEvent import io.github.wulkanowy.data.pojos.LoginEvent

View File

@ -1,23 +0,0 @@
package io.github.wulkanowy.data.api.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Mapping(
@SerialName("endpoints")
val endpoints: Map<String, Map<String, Map<String, String>>>,
@SerialName("vTokens")
val vTokens: Map<String, Map<String, Map<String, String>>>,
@SerialName("vTokenScheme")
val vTokenScheme: Map<String, Map<String, String>> = emptyMap(),
@SerialName("vHeaders")
val vHeaders: Map<String, Map<String, Map<String, String>>> = emptyMap(),
@SerialName("responseMap")
val responseMap: Map<String, Map<String, Map<String, Map<String, String>>>> = emptyMap(),
)

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.School import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@ -11,5 +12,16 @@ import javax.inject.Singleton
interface SchoolDao : BaseDao<School> { interface SchoolDao : BaseDao<School> {
@Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId")
fun load(studentId: Int, classId: Int): Flow<School?> fun loadWithClassId(studentId: Int, classId: Int): Flow<School?>
@Query("SELECT * FROM School WHERE student_id = :studentId")
fun loadNoClassId(studentId: Int): Flow<School?>
fun load(student: Student): Flow<School?> {
return if (student.isEduOne == true) {
loadNoClassId(student.studentId)
} else {
loadWithClassId(student.studentId, student.classId)
}
}
} }

View File

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
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 javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@ -14,6 +15,17 @@ 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) OR (student_id = :studentId AND class_id = 0)") @Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId)")
suspend fun loadAll(studentId: Int, classId: Int): List<Semester> suspend fun loadAllWithClassId(studentId: Int, classId: Int): List<Semester>
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
suspend fun loadAllNoClassId(studentId: Int): List<Semester>
suspend fun loadAll(student: Student): List<Semester> {
return if (student.isEduOne == true) {
loadAllNoClassId(student.studentId)
} else {
loadAllWithClassId(student.studentId, student.classId)
}
}
} }

View File

@ -47,13 +47,9 @@ 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) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)") @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id)")
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>> abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
@Transaction
@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>>
@Query("UPDATE Students SET is_current = 1 WHERE id = :id") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
abstract suspend fun updateCurrent(id: Long) abstract suspend fun updateCurrent(id: Long)

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.data.db.entities.Teacher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@ -11,5 +12,16 @@ import javax.inject.Singleton
interface TeacherDao : BaseDao<Teacher> { interface TeacherDao : BaseDao<Teacher> {
@Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId")
fun loadAll(studentId: Int, classId: Int): Flow<List<Teacher>> fun loadAllWithClassId(studentId: Int, classId: Int): Flow<List<Teacher>>
@Query("SELECT * FROM Teachers WHERE student_id = :studentId")
fun loadAllNoClassId(studentId: Int): Flow<List<Teacher>>
fun loadAll(student: Student): Flow<List<Teacher>> {
return if (student.isEduOne == true) {
loadAllNoClassId(student.studentId)
} else {
loadAllWithClassId(student.studentId, student.classId)
}
}
} }

View File

@ -6,6 +6,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration63 : AutoMigrationSpec { class Migration63 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) { override fun onPostMigrate(db: SupportSQLiteDatabase) {
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0") db.execSQL("DROP TABLE IF EXISTS `Semesters`")
db.execSQL("DROP TABLE IF EXISTS `School`")
db.execSQL("DROP TABLE IF EXISTS `Teachers`")
db.execSQL("CREATE TABLE IF NOT EXISTS `Semesters` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)")
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `Semesters` (`student_id`, `diary_id`, `kindergarten_diary_id`, `semester_id`)")
db.execSQL("CREATE TABLE IF NOT EXISTS `School` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
db.execSQL("CREATE TABLE IF NOT EXISTS `Teachers` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
db.execSQL("UPDATE Students SET is_edu_one = NULL")
} }
} }

View File

@ -0,0 +1,4 @@
package io.github.wulkanowy.data.exceptions
class NoSuchStudentException(id: Long) :
Exception("There is no student with id $id in database")

View File

@ -0,0 +1,34 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.networkBoundResource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AdminMessageRepository @Inject constructor(
private val adminMessageService: AdminMessageService,
private val adminMessageDao: AdminMessageDao,
) {
private val saveFetchResultMutex = Mutex()
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { false },
query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true },
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
},
)
.filterNot { it is Resource.Intermediate }
}

View File

@ -9,7 +9,6 @@ import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer import com.fredporciuncula.flow.preferences.Serializer
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.api.models.Mapping
import io.github.wulkanowy.data.enums.AppTheme import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeColorTheme
@ -376,15 +375,6 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
var mapping: Mapping?
get() {
val value = sharedPref.getString("mapping", null)
return value?.let { json.decodeFromString(it) }
}
set(value) = sharedPref.edit(commit = true) {
putString("mapping", value?.let { json.encodeToString(it) })
}
init { init {
if (installationId.isEmpty()) { if (installationId.isEmpty()) {
installationId = UUID.randomUUID().toString() installationId = UUID.randomUUID().toString()

View File

@ -36,7 +36,7 @@ class SchoolRepository @Inject constructor(
) )
it == null || forceRefresh || isExpired it == null || forceRefresh || isExpired
}, },
query = { schoolDb.load(semester.studentId, semester.classId) }, query = { schoolDb.load(student) },
fetch = { fetch = {
wulkanowySdkFactory.create(student, semester) wulkanowySdkFactory.create(student, semester)
.getSchool() .getSchool()

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.api.services.SchoolsService import io.github.wulkanowy.data.api.SchoolsService
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.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters

View File

@ -27,11 +27,11 @@ class SemesterRepository @Inject constructor(
forceRefresh: Boolean = false, forceRefresh: Boolean = false,
refreshOnNoCurrent: Boolean = false refreshOnNoCurrent: Boolean = false
) = withContext(dispatchers.io) { ) = withContext(dispatchers.io) {
val semesters = semesterDb.loadAll(student.studentId, student.classId) val semesters = semesterDb.loadAll(student)
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
refreshSemesters(student) refreshSemesters(student)
semesterDb.loadAll(student.studentId, student.classId) semesterDb.loadAll(student)
} else semesters } else semesters
} }
@ -69,7 +69,7 @@ class SemesterRepository @Inject constructor(
return return
} }
val old = semesterDb.loadAll(student.studentId, student.classId) val old = semesterDb.loadAll(student)
semesterDb.removeOldAndSaveNew( semesterDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new, oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old, newItems = new uniqueSubtract old,

View File

@ -12,6 +12,7 @@ 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.exceptions.NoSuchStudentException
import io.github.wulkanowy.data.mappers.mapToEntities 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
@ -65,7 +66,8 @@ class StudentRepository @Inject constructor(
.mapToPojo(password) .mapToPojo(password)
.also { it.logErrors() } .also { it.logErrors() }
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> { @Deprecated("Semesters are not synced within this method and students with empty semesters are not returned")
suspend fun getSavedStudentsWithSemesters(decryptPass: Boolean = true): List<StudentWithSemesters> {
return studentDb.loadStudentsWithSemesters().map { (student, semesters) -> return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
StudentWithSemesters( StudentWithSemesters(
student = student.apply { student = student.apply {
@ -80,22 +82,25 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true): StudentWithSemesters? = suspend fun getSavedStudents(decryptPass: Boolean = true): List<Student> {
studentDb.loadStudentWithSemestersById(id).let { res -> val students = studentDb.loadAll()
StudentWithSemesters( if (!decryptPass) return students
student = res.keys.firstOrNull() ?: return null,
semesters = res.values.first(), return students.map { student ->
) if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
}.apply { return@map student
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { }
student.password = withContext(dispatchers.io) {
student.apply {
password = withContext(dispatchers.io) {
scrambler.decrypt(student.password) scrambler.decrypt(student.password)
} }
} }
} }
}
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() val student = studentDb.loadById(id) ?: throw NoSuchStudentException(id)
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
student.password = withContext(dispatchers.io) { student.password = withContext(dispatchers.io) {
@ -123,7 +128,7 @@ class StudentRepository @Inject constructor(
return return
} }
val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId) val currentStudentSemesters = semesterDb.loadAll(student)
if (currentStudentSemesters.isEmpty()) { if (currentStudentSemesters.isEmpty()) {
Timber.d("Check isAuthorized: apply empty semesters workaround") Timber.d("Check isAuthorized: apply empty semesters workaround")
semesterDb.insertSemesters( semesterDb.insertSemesters(
@ -181,8 +186,8 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) { suspend fun switchStudent(student: Student) {
studentDb.switchCurrent(studentWithSemesters.student.id) studentDb.switchCurrent(student.id)
} }
suspend fun logoutStudent(student: Student) = studentDb.delete(student) suspend fun logoutStudent(student: Student) = studentDb.delete(student)
@ -190,8 +195,8 @@ class StudentRepository @Inject constructor(
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) = suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
studentDb.update(studentNickAndAvatar) studentDb.update(studentNickAndAvatar)
suspend fun isOneUniqueStudent() = getSavedStudents(false) suspend fun isOneUniqueStudent() = studentDb.loadAll()
.distinctBy { it.student.studentName }.size == 1 .distinctBy { it.studentName }.size == 1
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
wulkanowySdkFactory.create(student, semester) wulkanowySdkFactory.create(student, semester)
@ -199,7 +204,7 @@ class StudentRepository @Inject constructor(
suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) { suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) {
val wulkanowySdk = wulkanowySdkFactory.create(student, semester) val wulkanowySdk = wulkanowySdkFactory.create(student, semester)
val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() } val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() }
.onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") } .onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") }
.getOrNull() ?: return .getOrNull() ?: return
@ -209,7 +214,7 @@ class StudentRepository @Inject constructor(
studentDb.update(studentName) studentDb.update(studentName)
semesterDb.removeOldAndSaveNew( semesterDb.removeOldAndSaveNew(
oldItems = semesterDb.loadAll(student.studentId, semester.classId), oldItems = semesterDb.loadAll(student),
newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId) newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
) )
} }

View File

@ -35,7 +35,7 @@ class TeacherRepository @Inject constructor(
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { teacherDb.loadAll(semester.studentId, semester.classId) }, query = { teacherDb.loadAll(student) },
fetch = { fetch = {
wulkanowySdkFactory.create(student, semester) wulkanowySdkFactory.create(student, semester)
.getTeachers() .getTeachers()

View File

@ -1,66 +0,0 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.api.models.Mapping
import io.github.wulkanowy.data.api.services.WulkanowyService
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.sync.Mutex
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WulkanowyRepository @Inject constructor(
private val wulkanowyService: WulkanowyService,
private val adminMessageDao: AdminMessageDao,
private val preferencesRepository: PreferencesRepository,
private val refreshHelper: AutoRefreshHelper,
) {
private val saveFetchResultMutex = Mutex()
private val cacheKey = "mapping_refresh_key"
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { false },
query = { adminMessageDao.loadAll() },
fetch = { wulkanowyService.getAdminMessages() },
shouldFetch = { true },
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
},
)
.filterNot { it is Resource.Intermediate }
suspend fun getMapping(): Mapping? {
var savedMapping = preferencesRepository.mapping
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey)
)
if (savedMapping == null || isExpired) {
fetchMapping()
savedMapping = preferencesRepository.mapping
}
return savedMapping
}
suspend fun fetchMapping() {
runCatching { wulkanowyService.getMapping() }
.onFailure { Timber.e(it) }
.onSuccess {
preferencesRepository.mapping = it
refreshHelper.updateLastRefreshTimestamp(cacheKey)
}
}
}

View File

@ -5,14 +5,14 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageType import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.data.mapResourceData import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.repositories.AdminMessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.WulkanowyRepository
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
class GetAppropriateAdminMessageUseCase @Inject constructor( class GetAppropriateAdminMessageUseCase @Inject constructor(
private val wulkanowyRepository: WulkanowyRepository, private val adminMessageRepository: AdminMessageRepository,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val appInfo: AppInfo private val appInfo: AppInfo
) { ) {
@ -22,7 +22,7 @@ class GetAppropriateAdminMessageUseCase @Inject constructor(
} }
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> { operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
return wulkanowyRepository.getAdminMessages().mapResourceData { adminMessages -> return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
adminMessages adminMessages
.asSequence() .asSequence()
.filter { it.isNotDismissed() } .filter { it.isNotDismissed() }

View File

@ -59,7 +59,7 @@ class GetMailboxByStudentUseCase @Inject constructor(
private fun String.getUnauthorizedVersion(): String { private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ") return normalizeStudentName().split(" ")
.joinToString(" ") { .joinToString(" ") {
it.firstOrNull()?.toString().orEmpty() + "*".repeat((it.length - 1).coerceAtLeast(0)) it.first() + "*".repeat(it.length - 1)
} }
} }
} }

View File

@ -33,7 +33,7 @@ class AccountPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudents(false) } resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
.logResourceStatus("load account data") .logResourceStatus("load account data")
.onResourceSuccess { view?.updateData(createAccountItems(it)) } .onResourceSuccess { view?.updateData(createAccountItems(it)) }
.onResourceError(errorHandler::dispatch) .onResourceError(errorHandler::dispatch)

View File

@ -1,9 +1,15 @@
package io.github.wulkanowy.ui.modules.account.accountdetails package io.github.wulkanowy.ui.modules.account.accountdetails
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceLoading
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -14,6 +20,7 @@ import javax.inject.Inject
class AccountDetailsPresenter @Inject constructor( class AccountDetailsPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val semeRepository: SemesterRepository,
private val syncManager: SyncManager private val syncManager: SyncManager
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) { ) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
@ -46,7 +53,12 @@ class AccountDetailsPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) } resourceFlow {
val student = studentRepository.getStudentById(studentId ?: -1)
val semesters = semeRepository.getSemesters(student)
StudentWithSemesters(student, semesters)
}
.logResourceStatus("loading account details view") .logResourceStatus("loading account details view")
.onResourceLoading { .onResourceLoading {
view?.run { view?.run {
@ -85,7 +97,7 @@ class AccountDetailsPresenter @Inject constructor(
Timber.i("Select student ${studentWithSemesters!!.student.id}") Timber.i("Select student ${studentWithSemesters!!.student.id}")
resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) } resourceFlow { studentRepository.switchStudent(studentWithSemesters!!.student) }
.logResourceStatus("change student") .logResourceStatus("change student")
.onResourceSuccess { view?.recreateMainView() } .onResourceSuccess { view?.recreateMainView() }
.onResourceNotLoading { view?.popViewToMain() } .onResourceNotLoading { view?.popViewToMain() }
@ -122,10 +134,12 @@ class AccountDetailsPresenter @Inject constructor(
syncManager.stopSyncWorker() syncManager.stopSyncWorker()
openClearLoginView() openClearLoginView()
} }
studentWithSemesters?.student?.isCurrent == true -> { studentWithSemesters?.student?.isCurrent == true -> {
Timber.i("Logout result: Logout student and switch to another") Timber.i("Logout result: Logout student and switch to another")
recreateMainView() recreateMainView()
} }
else -> { else -> {
Timber.i("Logout result: Logout student") Timber.i("Logout result: Logout student")
recreateMainView() recreateMainView()

View File

@ -1,8 +1,12 @@
package io.github.wulkanowy.ui.modules.account.accountquick package io.github.wulkanowy.ui.modules.account.accountquick
import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.account.AccountItem import io.github.wulkanowy.ui.modules.account.AccountItem
@ -40,7 +44,7 @@ class AccountQuickPresenter @Inject constructor(
return return
} }
resourceFlow { studentRepository.switchStudent(studentWithSemesters) } resourceFlow { studentRepository.switchStudent(studentWithSemesters.student) }
.logResourceStatus("change student") .logResourceStatus("change student")
.onResourceSuccess { view?.recreateMainView() } .onResourceSuccess { view?.recreateMainView() }
.onResourceNotLoading { view?.popView() } .onResourceNotLoading { view?.popView() }

View File

@ -59,7 +59,7 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
webView = this webView = this
with(settings) { with(settings) {
javaScriptEnabled = true javaScriptEnabled = true
userAgentString = wulkanowySdkFactory.createBase().userAgent userAgentString = wulkanowySdkFactory.create().userAgent
} }
webViewClient = object : WebViewClient() { webViewClient = object : WebViewClient() {

View File

@ -30,7 +30,6 @@ 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.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
import io.github.wulkanowy.ui.modules.panicmode.PanicModeFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.capitalise
@ -126,7 +125,6 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
mainActivity.pushView(ConferenceFragment.newInstance()) mainActivity.pushView(ConferenceFragment.newInstance())
} }
onAdminMessageClickListener = presenter::onAdminMessageSelected onAdminMessageClickListener = presenter::onAdminMessageSelected
onPanicButtonClickListener = presenter::onPanicButtonClicked
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
@ -210,11 +208,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
binding = binding.dashboardErrorAdminMessage, binding = binding.dashboardErrorAdminMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected, onAdminMessageClickListener = presenter::onAdminMessageSelected,
onPanicButtonClickListener = presenter::onPanicButtonClicked, ).bind(adminMessageItem.adminMessage)
).bind(
item = adminMessageItem.adminMessage,
showPanicButton = true,
)
} }
} }
@ -242,10 +236,6 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
requireContext().openInternetBrowser(url) requireContext().openInternetBrowser(url)
} }
override fun openPanicWebView(url: String) {
(requireActivity() as MainActivity).pushView(PanicModeFragment.newInstance(url))
}
override fun onDestroyView() { override fun onDestroyView() {
dashboardAdapter.clearTimers() dashboardAdapter.clearTimers()
presenter.onDetachView() presenter.onDetachView()

View File

@ -11,7 +11,6 @@ import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.flatResourceFlow import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.mapResourceData import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.ConferenceRepository import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.ExamRepository
@ -24,7 +23,6 @@ import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -46,7 +44,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber import timber.log.Timber
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
@ -285,22 +282,6 @@ class DashboardPresenter @Inject constructor(
url?.let { view?.openInternetBrowser(it) } url?.let { view?.openInternetBrowser(it) }
} }
fun onPanicButtonClicked() {
resourceFlow { studentRepository.getCurrentStudent() }
.onResourceError { errorHandler.dispatch(it) }
.onResourceSuccess {
val baseUrl = it.scrapperBaseUrl.toHttpUrl()
val urlToOpen = baseUrl.newBuilder()
.host("uonetplus${it.scrapperDomainSuffix}.${baseUrl.host}")
.addPathSegment(it.symbol)
.build()
.toString()
view?.openPanicWebView(urlToOpen)
}
.launch("panic_button")
}
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow { flow {
val selectedTiles = selectedDashboardTiles val selectedTiles = selectedDashboardTiles

View File

@ -31,6 +31,4 @@ interface DashboardView : BaseView {
fun openNotificationsCenterView() fun openNotificationsCenterView()
fun openInternetBrowser(url: String) fun openInternetBrowser(url: String)
fun openPanicWebView(url: String)
} }

View File

@ -59,8 +59,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var onAdminMessageClickListener: (String?) -> Unit = {} var onAdminMessageClickListener: (String?) -> Unit = {}
var onPanicButtonClickListener: () -> Unit = {}
var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {} var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {}
val items = mutableListOf<DashboardItem>() val items = mutableListOf<DashboardItem>()
@ -88,46 +86,35 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
DashboardItem.Type.ACCOUNT.ordinal -> AccountViewHolder( DashboardItem.Type.ACCOUNT.ordinal -> AccountViewHolder(
ItemDashboardAccountBinding.inflate(inflater, parent, false) ItemDashboardAccountBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder( DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder(
ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false) ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.GRADES.ordinal -> GradesViewHolder( DashboardItem.Type.GRADES.ordinal -> GradesViewHolder(
ItemDashboardGradesBinding.inflate(inflater, parent, false) ItemDashboardGradesBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder( DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder(
ItemDashboardLessonsBinding.inflate(inflater, parent, false) ItemDashboardLessonsBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder( DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder(
ItemDashboardHomeworkBinding.inflate(inflater, parent, false) ItemDashboardHomeworkBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder( DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder(
ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false) ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder( DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder(
ItemDashboardExamsBinding.inflate(inflater, parent, false) ItemDashboardExamsBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder( DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
ItemDashboardConferencesBinding.inflate(inflater, parent, false) ItemDashboardConferencesBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder( DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false), ItemDashboardAdminMessageBinding.inflate(inflater, parent, false),
onAdminMessageDismissClickListener = onAdminMessageDismissClickListener, onAdminMessageDismissClickListener = onAdminMessageDismissClickListener,
onAdminMessageClickListener = onAdminMessageClickListener, onAdminMessageClickListener = onAdminMessageClickListener,
onPanicButtonClickListener = onPanicButtonClickListener,
) )
DashboardItem.Type.ADS.ordinal -> AdsViewHolder( DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
ItemDashboardAdsBinding.inflate(inflater, parent, false) ItemDashboardAdsBinding.inflate(inflater, parent, false)
) )
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
} }
@ -142,11 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position) is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
is ExamsViewHolder -> bindExamsViewHolder(holder, position) is ExamsViewHolder -> bindExamsViewHolder(holder, position)
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position) is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
is AdminMessageViewHolder -> holder.bind( is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage)
(items[position] as DashboardItem.AdminMessages).adminMessage,
showPanicButton = true
)
is AdsViewHolder -> bindAdsViewHolder(holder, position) is AdsViewHolder -> bindAdsViewHolder(holder, position)
} }
} }
@ -257,15 +240,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
attendancePercentage == null || attendancePercentage == .0 -> { attendancePercentage == null || attendancePercentage == .0 -> {
root.context.getThemeAttrColor(R.attr.colorOnSurface) root.context.getThemeAttrColor(R.attr.colorOnSurface)
} }
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> { attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
root.context.getThemeAttrColor(R.attr.colorPrimary) root.context.getThemeAttrColor(R.attr.colorPrimary)
} }
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> { attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
root.context.getThemeAttrColor(R.attr.colorTimetableChange) root.context.getThemeAttrColor(R.attr.colorTimetableChange)
} }
else -> root.context.getThemeAttrColor(R.attr.colorOnSurface) else -> root.context.getThemeAttrColor(R.attr.colorOnSurface)
} }
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) { val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
@ -356,28 +336,24 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
binding.dashboardLessonsItemTitleTomorrow.isVisible = false binding.dashboardLessonsItemTitleTomorrow.isVisible = false
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
} }
tomorrowTimetable.isNotEmpty() -> { tomorrowTimetable.isNotEmpty() -> {
dateToNavigate = tomorrowDate dateToNavigate = tomorrowDate
updateLessonView(item, tomorrowTimetable, binding) updateLessonView(item, tomorrowTimetable, binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true binding.dashboardLessonsItemTitleTomorrow.isVisible = true
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
} }
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> { currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
dateToNavigate = currentDate dateToNavigate = currentDate
updateLessonView(item, emptyList(), binding, currentDayHeader) updateLessonView(item, emptyList(), binding, currentDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false binding.dashboardLessonsItemTitleTomorrow.isVisible = false
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
} }
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> { tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
dateToNavigate = tomorrowDate dateToNavigate = tomorrowDate
updateLessonView(item, emptyList(), binding, tomorrowDayHeader) updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true binding.dashboardLessonsItemTitleTomorrow.isVisible = true
binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
} }
else -> { else -> {
dateToNavigate = currentDate dateToNavigate = currentDate
updateLessonView(item, emptyList(), binding) updateLessonView(item, emptyList(), binding)
@ -485,7 +461,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
firstTitleText = firstTitleText =
context.getString(R.string.dashboard_timetable_first_lesson_title_moment) context.getString(R.string.dashboard_timetable_first_lesson_title_moment)
} }
minutesToStartLesson < 240 -> { minutesToStartLesson < 240 -> {
firstTitleAndValueTextColor = firstTitleAndValueTextColor =
context.getThemeAttrColor(R.attr.colorOnSurface) context.getThemeAttrColor(R.attr.colorOnSurface)
@ -493,7 +468,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
firstTitleText = firstTitleText =
context.getString(R.string.dashboard_timetable_first_lesson_title_soon) context.getString(R.string.dashboard_timetable_first_lesson_title_soon)
} }
else -> { else -> {
firstTitleAndValueTextColor = firstTitleAndValueTextColor =
context.getThemeAttrColor(R.attr.colorOnSurface) context.getThemeAttrColor(R.attr.colorOnSurface)

View File

@ -13,10 +13,9 @@ class AdminMessageViewHolder(
private val binding: ItemDashboardAdminMessageBinding, private val binding: ItemDashboardAdminMessageBinding,
private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit, private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit,
private val onAdminMessageClickListener: (String?) -> Unit, private val onAdminMessageClickListener: (String?) -> Unit,
private val onPanicButtonClickListener: () -> Unit,
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: AdminMessage?, showPanicButton: Boolean = false) { fun bind(item: AdminMessage?) {
item ?: return item ?: return
val context = binding.root.context val context = binding.root.context
@ -49,14 +48,10 @@ class AdminMessageViewHolder(
dashboardAdminMessageItemClose.setOnClickListener { dashboardAdminMessageItemClose.setOnClickListener {
onAdminMessageDismissClickListener(item) onAdminMessageDismissClickListener(item)
} }
dashboardPanicSection.root.isVisible = showPanicButton
dashboardPanicSection.dashboardPanicButton.setOnClickListener {
onPanicButtonClickListener()
}
dashboardAdminMessage.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) }) root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url -> item.destinationUrl?.let { url ->
dashboardAdminMessage.setOnClickListener { onAdminMessageClickListener(url) } root.setOnClickListener { onAdminMessageClickListener(url) }
} }
} }
} }

View File

@ -118,6 +118,5 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
inAppUpdateHelper.onResume() inAppUpdateHelper.onResume()
presenter.updateSdkMappings()
} }
} }

View File

@ -1,15 +1,12 @@
package io.github.wulkanowy.ui.modules.login package io.github.wulkanowy.ui.modules.login
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.WulkanowyRepository
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 timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class LoginPresenter @Inject constructor( class LoginPresenter @Inject constructor(
private val wulkanowyRepository: WulkanowyRepository,
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository studentRepository: StudentRepository
) : BasePresenter<LoginView>(errorHandler, studentRepository) { ) : BasePresenter<LoginView>(errorHandler, studentRepository) {
@ -19,11 +16,4 @@ class LoginPresenter @Inject constructor(
view.initView() view.initView()
Timber.i("Login view was initialized") Timber.i("Login view was initialized")
} }
fun updateSdkMappings() {
presenterScope.launch {
runCatching { wulkanowyRepository.fetchMapping() }
.onFailure { Timber.e(it) }
}
}
} }

View File

@ -238,7 +238,6 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
binding = binding.loginFormMessage, binding = binding.loginFormMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected, onAdminMessageClickListener = presenter::onAdminMessageSelected,
onPanicButtonClickListener = {},
).bind(message) ).bind(message)
binding.loginFormMessage.root.isVisible = message != null binding.loginFormMessage.root.isVisible = message != null
} }

View File

@ -118,7 +118,6 @@ class LoginStudentSelectFragment :
binding = binding.loginStudentSelectAdminMessage, binding = binding.loginStudentSelectAdminMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected, onAdminMessageClickListener = presenter::onAdminMessageSelected,
onPanicButtonClickListener = {},
).bind(adminMessage) ).bind(adminMessage)
binding.loginStudentSelectAdminMessage.root.isVisible = adminMessage != null binding.loginStudentSelectAdminMessage.root.isVisible = adminMessage != null
} }

View File

@ -80,7 +80,7 @@ class LoginStudentSelectPresenter @Inject constructor(
private fun loadData() { private fun loadData() {
resetSelectedState() resetSelectedState()
resourceFlow { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }.onEach {
students = it.dataOrNull.orEmpty() students = it.dataOrNull.orEmpty()
when (it) { when (it) {
is Resource.Loading -> Timber.d("Login student select students load started") is Resource.Loading -> Timber.d("Login student select students load started")

View File

@ -188,7 +188,6 @@ class LoginSymbolFragment :
binding = binding.loginSymbolAdminMessage, binding = binding.loginSymbolAdminMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected, onAdminMessageClickListener = presenter::onAdminMessageSelected,
onPanicButtonClickListener = {},
).bind(adminMessage) ).bind(adminMessage)
binding.loginSymbolAdminMessage.root.isVisible = adminMessage != null binding.loginSymbolAdminMessage.root.isVisible = adminMessage != null
} }

View File

@ -35,7 +35,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }.onEach {
when (it) { when (it) {
is Resource.Loading -> Timber.d("Lucky number widget configure students data load") is Resource.Loading -> Timber.d("Lucky number widget configure students data load")
is Resource.Success -> { is Resource.Success -> {

View File

@ -132,7 +132,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking { private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking {
try { try {
val students = studentRepository.getSavedStudents() val students = studentRepository.getSavedStudents()
val student = students.singleOrNull { it.student.id == studentId }?.student val student = students.singleOrNull { it.id == studentId }
val currentStudent = when { val currentStudent = when {
student != null -> student student != null -> student
studentId != 0L && studentRepository.isCurrentStudentSet() -> { studentId != 0L && studentRepository.isCurrentStudentSet() -> {

View File

@ -138,7 +138,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
inAppUpdateHelper.onResume() inAppUpdateHelper.onResume()
presenter.updateSdkMappings()
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -6,7 +6,6 @@ import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.WulkanowyRepository
import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.data.resourceFlow
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
@ -30,7 +29,6 @@ class MainPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val wulkanowyRepository: WulkanowyRepository,
private val syncManager: SyncManager, private val syncManager: SyncManager,
private val analytics: AnalyticsHelper, private val analytics: AnalyticsHelper,
private val json: Json, private val json: Json,
@ -87,7 +85,7 @@ class MainPresenter @Inject constructor(
return return
} }
resourceFlow { studentRepository.getSavedStudents(false) } resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
.logResourceStatus("load student avatar") .logResourceStatus("load student avatar")
.onResourceSuccess { .onResourceSuccess {
studentsWitSemesters = it studentsWitSemesters = it
@ -201,11 +199,4 @@ class MainPresenter @Inject constructor(
.onFailure { errorHandler.dispatch(it) } .onFailure { errorHandler.dispatch(it) }
} }
} }
fun updateSdkMappings() {
presenterScope.launch {
runCatching { wulkanowyRepository.fetchMapping() }
.onFailure { Timber.e(it) }
}
}
} }

View File

@ -27,7 +27,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.ui.modules.panicmode.PanicModeFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
@ -133,7 +132,6 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
) )
messageTabErrorRetry.setOnClickListener { presenter.onRetry() } messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
messageTabPanicSection.dashboardPanicButton.setOnClickListener { presenter.onPanicButtonClicked() }
} }
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle -> setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
@ -285,10 +283,6 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
) )
} }
override fun openPanicWebView(url: String) {
(requireActivity() as MainActivity).pushView(PanicModeFragment.newInstance(url))
}
override fun hideKeyboard() { override fun hideKeyboard() {
activity?.hideSoftInput() activity?.hideSoftInput()
} }

View File

@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.xdrop.fuzzywuzzy.FuzzySearch import me.xdrop.fuzzywuzzy.FuzzySearch
import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.pow import kotlin.math.pow
@ -430,20 +429,4 @@ class MessageTabPresenter @Inject constructor(
+ dateRatio.toDouble().pow(2) * 2 + dateRatio.toDouble().pow(2) * 2
).toInt() ).toInt()
} }
fun onPanicButtonClicked() {
resourceFlow { studentRepository.getCurrentStudent() }
.onResourceError { errorHandler.dispatch(it) }
.onResourceSuccess {
val baseUrl = it.scrapperBaseUrl.toHttpUrl()
val urlToOpen = baseUrl.newBuilder()
.host("uonetplus${it.scrapperDomainSuffix}-wiadomosciplus.${baseUrl.host}")
.addPathSegment(it.symbol)
.build()
.toString()
view?.openPanicWebView(urlToOpen)
}
.launch("panic_button")
}
} }

View File

@ -50,6 +50,4 @@ interface MessageTabView : BaseView {
fun showRecyclerBottomPadding(show: Boolean) fun showRecyclerBottomPadding(show: Boolean)
fun showMailboxChooser(mailboxes: List<Mailbox>) fun showMailboxChooser(mailboxes: List<Mailbox>)
fun openPanicWebView(url: String)
} }

View File

@ -1,99 +0,0 @@
package io.github.wulkanowy.ui.modules.panicmode
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.databinding.FragmentPanicModeBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
import io.github.wulkanowy.utils.openInternetBrowser
import javax.inject.Inject
@AndroidEntryPoint
class PanicModeFragment : BaseFragment<FragmentPanicModeBinding>(R.layout.fragment_panic_mode),
MainView.TitledView {
@Inject
lateinit var wulkanowySdkFactory: WulkanowySdkFactory
@Inject
lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy
private var webView: WebView? = null
override val titleStringId: Int get() = R.string.panic_mode_title
companion object {
private const val PANIC_URL = "panic_mode_url"
fun newInstance(url: String?): PanicModeFragment {
return PanicModeFragment().apply {
arguments = bundleOf(PANIC_URL to url)
}
}
}
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentPanicModeBinding.bind(view)
binding.panicModeRefresh.setOnClickListener {
binding.panicModeWebview.loadUrl(
binding.panicModeWebview.url ?: arguments?.getString(PANIC_URL).orEmpty()
)
}
binding.panicModeBack.setOnClickListener { binding.panicModeWebview.goBack() }
binding.panicModeHome.setOnClickListener {
binding.panicModeWebview.loadUrl(
arguments?.getString(PANIC_URL).orEmpty()
)
}
binding.panicModeForward.setOnClickListener { binding.panicModeWebview.goForward() }
binding.panicModeShare.setOnClickListener {
requireContext().openInternetBrowser(
binding.panicModeWebview.url.toString(),
)
}
val onBackPressedCallback = requireActivity().onBackPressedDispatcher
.addCallback(viewLifecycleOwner) {
binding.panicModeWebview.goBack()
}
with(binding.panicModeWebview) {
webView = this
with(settings) {
javaScriptEnabled = true
userAgentString = wulkanowySdkFactory.createBase().userAgent
}
webViewClient = object : WebViewClient() {
override fun doUpdateVisitedHistory(
view: WebView?,
url: String?,
isReload: Boolean
) {
binding.panicModeBack.isEnabled = binding.panicModeWebview.canGoBack()
binding.panicModeForward.isEnabled = binding.panicModeWebview.canGoForward()
onBackPressedCallback.isEnabled = binding.panicModeWebview.canGoBack()
}
}
loadUrl(arguments?.getString(PANIC_URL).orEmpty())
}
}
override fun onDestroy() {
webkitCookieManagerProxy.webkitCookieManager?.flush()
webView?.destroy()
super.onDestroy()
}
}

View File

@ -42,22 +42,24 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
resourceFlow { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
when (it) { .onEach {
is Resource.Loading -> Timber.d("Timetable widget configure students data load") when (it) {
is Resource.Success -> { is Resource.Loading -> Timber.d("Timetable widget configure students data load")
val selectedStudentId = appWidgetId?.let { id -> is Resource.Success -> {
sharedPref.getLong(getStudentWidgetKey(id), 0) val selectedStudentId = appWidgetId?.let { id ->
} ?: -1 sharedPref.getLong(getStudentWidgetKey(id), 0)
when { } ?: -1
it.data.isEmpty() -> view?.openLoginView() when {
it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student) it.data.isEmpty() -> view?.openLoginView()
else -> view?.updateData(it.data, selectedStudentId) it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student)
else -> view?.updateData(it.data, selectedStudentId)
}
} }
is Resource.Error -> errorHandler.dispatch(it.error)
} }
is Resource.Error -> errorHandler.dispatch(it.error) }.launch()
}
}.launch()
} }
private fun registerStudent(student: Student?) { private fun registerStudent(student: Student?) {

View File

@ -95,7 +95,7 @@ class TimetableWidgetFactory(
private suspend fun getStudent(studentId: Long): Student? { private suspend fun getStudent(studentId: Long): Student? {
val students = studentRepository.getSavedStudents() val students = studentRepository.getSavedStudents()
return students.singleOrNull { it.student.id == studentId }?.student return students.singleOrNull { it.id == studentId }
} }
private suspend fun getLessons( private suspend fun getLessons(

View File

@ -2,7 +2,11 @@ package io.github.wulkanowy.ui.modules.timetablewidget
import android.app.PendingIntent import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetManager.* import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED
import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -22,7 +26,14 @@ import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -244,7 +255,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try {
val students = studentRepository.getSavedStudents(false) val students = studentRepository.getSavedStudents(false)
val student = students.singleOrNull { it.student.id == studentId }?.student val student = students.singleOrNull { it.id == studentId }
when { when {
student != null -> student student != null -> student
studentId != 0L && studentRepository.isCurrentStudentSet() -> { studentId != 0L && studentRepository.isCurrentStudentSet() -> {
@ -263,7 +274,10 @@ class TimetableWidgetProvider : BroadcastReceiver() {
} }
private fun setupAccountView( private fun setupAccountView(
context: Context, student: Student, remoteViews: RemoteViews, widgetId: Int context: Context,
student: Student,
remoteViews: RemoteViews,
widgetId: Int
) { ) {
val accountInitials = getAccountInitials(student.nickOrName) val accountInitials = getAccountInitials(student.nickOrName)
val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId) val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId)

View File

@ -30,10 +30,6 @@ fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): Strin
return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}" return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
} }
fun getRefreshKey(name: String): String {
return name
}
class AutoRefreshHelper @Inject constructor( class AutoRefreshHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val sharedPref: SharedPrefProvider private val sharedPref: SharedPrefProvider

View File

@ -1,5 +1,8 @@
Wersja 2.6.15 Wersja 2.6.1
naprawiliśmy moduł wiadomości dodaliśmy kalkulator frekwencji
— dodaliśmy wyświetlanie lekcji dodatkowych w planie lekcji
— ulepszyliśmy wyjaśnienie na ekranie z miejscem na wpisanie numeru PESEL
— naprawiliśmy rzadkie sytuacje, gdy plan lekcji nakładał się na informację o jego braku
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -60,16 +60,6 @@
tools:ignore="UseCompoundDrawables" tools:ignore="UseCompoundDrawables"
tools:visibility="invisible"> tools:visibility="invisible">
<include
android:id="@+id/message_tab_panic_section"
layout="@layout/item_dashboard_panic_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/dashboard_error_admin_message" />
<ImageView <ImageView
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="100dp"

View File

@ -1,69 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.modules.panicmode.PanicModeFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorControlHighlight">
<com.google.android.material.button.MaterialButton
android:id="@+id/panic_mode_share"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
app:icon="@drawable/ic_share"
app:iconTint="?colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/panic_mode_home"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
app:icon="@drawable/ic_all_home"
app:iconTint="?colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/panic_mode_refresh"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/logviewer_refresh"
app:icon="@drawable/ic_refresh"
app:iconTint="?colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/panic_mode_back"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
app:icon="@drawable/ic_chevron_left"
app:iconTint="?colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/panic_mode_forward"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
app:icon="@drawable/ic_chevron_right"
app:iconTint="?colorOnSurface" />
</LinearLayout>
<WebView
android:id="@+id/panic_mode_webview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

View File

@ -1,105 +1,87 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp">
<com.google.android.material.card.MaterialCardView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dashboard_admin_message" android:id="@+id/dashboard_admin_message_item_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp">
<ImageView
android:id="@+id/dashboard_admin_message_item_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_error"
app:layout_constraintBottom_toBottomOf="@id/dashboard_admin_message_item_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/dashboard_admin_message_item_title"
tools:ignore="ContentDescription"
tools:tint="@android:color/black" />
<androidx.constraintlayout.widget.ConstraintLayout <TextView
android:id="@+id/dashboard_admin_message_item_content" android:id="@+id/dashboard_admin_message_item_title"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/dashboard_admin_message_item_close"
app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<ImageView <ImageView
android:id="@+id/dashboard_admin_message_item_icon" android:id="@+id/dashboard_admin_message_item_close"
android:layout_width="24dp" android:layout_width="48dp"
android:layout_height="24dp" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginTop="4dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="4dp"
android:src="@drawable/ic_error" android:padding="12dp"
app:layout_constraintBottom_toBottomOf="@id/dashboard_admin_message_item_title" android:background="?selectableItemBackgroundBorderless"
app:layout_constraintStart_toStartOf="parent" android:src="@drawable/ic_close"
app:layout_constraintTop_toTopOf="@id/dashboard_admin_message_item_title" app:layout_constraintEnd_toEndOf="parent"
tools:ignore="ContentDescription" app:layout_constraintTop_toTopOf="parent"
tools:tint="@android:color/black" /> tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/dashboard_admin_message_item_title" android:id="@+id/dashboard_admin_message_item_description"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:textSize="18sp" android:layout_marginBottom="16dp"
android:textStyle="bold" android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/dashboard_admin_message_item_close" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/lorem" /> app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_title"
app:layout_constraintVertical_bias="0"
app:lineHeight="20dp"
tools:maxLines="5"
tools:text="@tools:sample/lorem/random" />
<ImageView <com.google.android.material.button.MaterialButton
android:id="@+id/dashboard_admin_message_item_close" android:id="@+id/dashboard_admin_message_item_dismiss"
android:layout_width="48dp" style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_height="48dp" android:layout_width="wrap_content"
android:layout_marginTop="4dp" android:layout_height="wrap_content"
android:layout_marginEnd="4dp" android:layout_marginTop="8dp"
android:background="?selectableItemBackgroundBorderless" android:layout_marginEnd="16dp"
android:padding="12dp" android:layout_marginBottom="8dp"
android:src="@drawable/ic_close" android:text="@android:string/ok"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"
tools:ignore="ContentDescription" /> app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_description"
app:layout_constraintVertical_bias="0" />
<TextView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/dashboard_admin_message_item_description" </com.google.android.material.card.MaterialCardView>
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_title"
app:layout_constraintVertical_bias="0"
app:lineHeight="20dp"
tools:maxLines="5"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.button.MaterialButton
android:id="@+id/dashboard_admin_message_item_dismiss"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@android:string/ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_description"
app:layout_constraintVertical_bias="0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<include
android:id="@+id/dashboard_panic_section"
layout="@layout/item_dashboard_panic_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/dashboard_error_admin_message" />
</LinearLayout>

View File

@ -1,28 +0,0 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp">
<LinearLayout
android:layout_margin="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Aplikacja nie działa?"
android:textSize="18sp"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/dashboard_panic_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Otwórz stronę dziennika" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -29,7 +29,7 @@
<string name="notifications_center_title">Centrum oznámení</string> <string name="notifications_center_title">Centrum oznámení</string>
<string name="menu_order_title">Konfigurace menu</string> <string name="menu_order_title">Konfigurace menu</string>
<!--Subtitles--> <!--Subtitles-->
<string name="grade_subtitle">Pololetí %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í žákovské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: &lt;b&gt;%1$s&lt;/b&gt;</string> <string name="login_header_symbol">Zadejte symbol ze stránky deníku: &lt;b&gt;%1$s&lt;/b&gt;</string>
@ -105,8 +105,8 @@
<string name="main_support_positive">Zapnout reklamy</string> <string name="main_support_positive">Zapnout reklamy</string>
<!--Grade--> <!--Grade-->
<string name="grade_header">Známka</string> <string name="grade_header">Známka</string>
<string name="grade_semester">Pololetí %d</string> <string name="grade_semester">Semestr %d</string>
<string name="grade_switch_semester">Změnit pololetí</string> <string name="grade_switch_semester">Změnit semestr</string>
<string name="grade_no_items">Žádné známky</string> <string name="grade_no_items">Žádné známky</string>
<string name="grade_weight">Váha</string> <string name="grade_weight">Váha</string>
<string name="grade_weight_value">Váha: %s</string> <string name="grade_weight_value">Váha: %s</string>
@ -125,16 +125,16 @@
<string name="grade_summary_calculated_average">Vypočítaný pololetní průměr</string> <string name="grade_summary_calculated_average">Vypočítaný pololetní průměr</string>
<string name="grade_summary_calculated_average_annual">Vypočítaný roční průměr</string> <string name="grade_summary_calculated_average_annual">Vypočítaný roční průměr</string>
<string name="grade_summary_calculated_average_help_dialog_title">Jak funguje vypočítaný průměr?</string> <string name="grade_summary_calculated_average_help_dialog_title">Jak funguje vypočítaný průměr?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\n<b>Průměr známek pouze z vybraného pololetí</b>:\n1. Výpočet váženého průměru pro každý předmět v daném pololetí\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 pololetí</b>:\n1. Výpočet váženého průměru pro každý předmět v pololetí 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za pololetí 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. pololetí 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>
<string name="grade_summary_final_average_help_dialog_message">Konečný průměr je aritmetický průměr vypočítaný ze všech aktuálně dostupných konečných známek v daném pololetí.\n\nSchéma výpočtu se skládá z následujících kroků:\n1. Sčítání konečných známek zadaných učiteli\n2. Děleno počtem předmětů, pro které už byly uděleny známky</string> <string name="grade_summary_final_average_help_dialog_message">Konečný průměr je aritmetický průměr vypočítaný ze všech aktuálně dostupných konečných známek v daném semestru.\n\nSchéma výpočtu se skládá z následujících kroků:\n1. Sčítání konečných známek zadaných učiteli\n2. Děleno počtem předmětů, pro které už byly uděleny známky</string>
<string name="grade_summary_final_average">Konečný průměr</string> <string name="grade_summary_final_average">Konečný průměr</string>
<string name="grade_summary_from_subjects">z %1$d z %2$d předmětů</string> <string name="grade_summary_from_subjects">z %1$d z %2$d předmětů</string>
<string name="grade_menu_summary">Shrnutí</string> <string name="grade_menu_summary">Shrnutí</string>
<string name="grade_menu_statistics">Třída</string> <string name="grade_menu_statistics">Třída</string>
<string name="grade_menu_read">Označit jako přečtené</string> <string name="grade_menu_read">Označit jako přečtené</string>
<string name="grade_statistics_partial">Částečně</string> <string name="grade_statistics_partial">Částečně</string>
<string name="grade_statistics_semester">Pololetí</string> <string name="grade_statistics_semester">Semestr</string>
<string name="grade_statistics_points">Body</string> <string name="grade_statistics_points">Body</string>
<string name="grade_statistics_legend">Vysvětlivky</string> <string name="grade_statistics_legend">Vysvětlivky</string>
<string name="grade_statistics_class_average">Průměr třídy: %1$s</string> <string name="grade_statistics_class_average">Průměr třídy: %1$s</string>
@ -499,7 +499,7 @@
<string name="mobile_devices_title">Mobilní přístup</string> <string name="mobile_devices_title">Mobilní přístup</string>
<string name="mobile_devices_no_items">Žádná zařízení</string> <string name="mobile_devices_no_items">Žádná zařízení</string>
<string name="mobile_devices_unregister">Zrušit registraci</string> <string name="mobile_devices_unregister">Zrušit registraci</string>
<string name="mobile_device_removed">Zařízení odstraněno</string> <string name="mobile_device_removed">Zařízení odstranění</string>
<string name="mobile_device_qr">QR kód</string> <string name="mobile_device_qr">QR kód</string>
<string name="mobile_device_token">Token</string> <string name="mobile_device_token">Token</string>
<string name="mobile_device_symbol">Symbol</string> <string name="mobile_device_symbol">Symbol</string>
@ -872,8 +872,6 @@
<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>
<string name="captcha_dialog_description"><b>Proč se mi to zobrazuje?</b>\nWebová stránka deníku, ze které Wulkanowy stahuje data, zobrazuje stejnou obrazovku jako výše, takže Wulkanowy ji musí také zobrazit, aby bylo možné získávat data z této stránky. Nedá se to obejít</string> <string name="captcha_dialog_description"><b>Proč se mi to zobrazuje?</b>\nWebová stránka deníku, ze které Wulkanowy stahuje data, zobrazuje stejnou obrazovku jako výše, takže Wulkanowy ji musí také zobrazit, aby bylo možné získávat data z této stránky. Nedá se to obejít</string>
<string name="captcha_verified_message">Úspěšně ověřeno</string> <string name="captcha_verified_message">Úspěšně ověřeno</string>
<!--Panic mode-->
<string name="panic_mode_title">Nouzový přístup</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Žádné internetové připojení</string> <string name="error_no_internet">Žádné internetové připojení</string>
<string name="error_invalid_device_datetime">Vyskytla se chyba. Zkontrolujte hodiny svého zařízení</string> <string name="error_invalid_device_datetime">Vyskytla se chyba. Zkontrolujte hodiny svého zařízení</string>

View File

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="sort_alphabetically">Alfabéticzno</string>
<string name="sort_by_date">Pòdług datë</string>
<string name="sort_by_average">Pòdług strzédny</string>
<string name="sort_by_attendance_percentage">Pòdług procentu bëtnoscë</string>
<string name="sort_by_subject_attendance_balance">Pòdług salda frekwencje na przibiorze</string>
<string-array name="app_theme_entries" tools:ignore="InconsistentArrays">
<item>Jôsny</item>
<item>Cemny</item>
<item>Cemny (AMOLED)</item>
</string-array>
<string-array name="app_language_entries">
<item>Systemòwi jãzëk</item>
<item>Polski</item>
<item>English</item>
<item>Pусский</item>
<item>Українська</item>
<item>Deutsch</item>
<item>Čeština</item>
<item>Slovenčina</item>
</string-array>
<string-array name="services_interval_entries">
<item>15 minutów</item>
<item>30 minutów</item>
<item>1 gòdzëna</item>
<item>2 gòdzënë</item>
<item>6 gòdzyn</item>
<item>12 gòdzyn</item>
<item>24 gòdzyn</item>
</string-array>
<string-array name="grade_modifier_entries">
<item>0,00</item>
<item>0,25</item>
<item>0,33</item>
<item>0,5</item>
<item>0,75</item>
</string-array>
<string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item>
<item>Wulkanowy</item>
<item>Farwa taksów w dzénnikù</item>
</string-array>
<string-array name="default_expand_grade_entries">
<item>Do 1 na rôz</item>
<item>Wiedno rozwiniãti</item>
<item>Rozwijanié bez grańców</item>
</string-array>
<string-array name="grade_average_mode_entries">
<item>Strzédnô taksów leno z wëbrónégò semestru</item>
<item>Strzédnô z strzédnëch z òbùch semestrów</item>
<item>Strzédnô wszëtczich taksów z całégò rokù</item>
</string-array>
<string-array name="timetable_show_gaps_entries">
<item>Nié pòkazuj</item>
<item>Leno midzë ùczbama</item>
<item>Przed a midzë ùczbama</item>
</string-array>
<string-array name="timetable_show_additional_lessons_entries">
<item>Nié pòkazuj</item>
<item>Pòkażë razã</item>
<item>Pòkôżë niżi zwëczajné ùczbë</item>
</string-array>
<string-array name="dashboard_tile_entries">
<item>Szczestlëwi numerk</item>
<item>Nieprzeczëtóné wiadë</item>
<item>Frekwencjô</item>
<item>Ùczbë</item>
<item>Taksë</item>
<item>Zadanié dodóm</item>
<item>Szkòlowi ògłos</item>
<item>Testë</item>
<item>Zéńdzenia</item>
</string-array>
</resources>

View File

@ -1,851 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Activity/Fragment title-->
<string name="login_title">Logòwanié</string>
<string name="main_title">Wulkanowy</string>
<string name="grade_title">Taksë</string>
<string name="attendance_title">Frekwencjô</string>
<string name="exam_title">Testë</string>
<string name="timetable_title">Plan zajmów</string>
<string name="settings_title">Nastôwë</string>
<string name="more_title">Wicy</string>
<string name="about_title">Ò aplikacëje</string>
<string name="logviewer_title">Pòkazéwôcz logów</string>
<string name="debug_title">Debùgòwanié</string>
<string name="notification_debug_title">Debùgòwanié wiadłów</string>
<string name="debug_cookies_clear">Rëmni kùszczi webview</string>
<string name="contributors_title">Wkłôdôrze</string>
<string name="license_title">Licencëje</string>
<string name="message_title">Wiadë</string>
<string name="send_message_title">Nowô wiada</string>
<string name="add_homework_title">Nowé zadanié dodóm</string>
<string name="note_title">Ùwôdżi i pòstãpë</string>
<string name="homework_title">Zadanié dodóm</string>
<string name="account_title">Czerownik kòntów</string>
<string name="account_quick_title">Wëbierzë kònto</string>
<string name="account_details_title">Drobnotë kònta</string>
<string name="student_info_title">Jinfòrmacëje ò ùczniu</string>
<string name="dashboard_title">Doma</string>
<string name="notifications_center_title">Centrum wiadłów</string>
<string name="menu_order_title">Kònfigùracëjô menu</string>
<!--Subtitles-->
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
<!--Login-->
<string name="login_header_default">Zalogùj sã brëkùjącë kònta ùcznia abò rodzëca</string>
<string name="login_header_symbol">Pòdôj symbòl z starnë dzénnika dlô kònta: &lt;b&gt;%1$s&lt;/b&gt;</string>
<string name="login_nickname_hint">Pòzwa brëkòwnika</string>
<string name="login_email_hint">Adresa E-mail</string>
<string name="login_login_pesel_email_hint">Login, PESEL abò adresa e-mail</string>
<string name="login_password_hint">Parola</string>
<string name="login_host_hint">Wariant dzénnika UONET+</string>
<string name="login_domain_suffix_hint">Niesztandardowi sufiks domenë</string>
<string name="login_type_api">Mòbilné API</string>
<string name="login_type_scrapper">Scraper</string>
<string name="login_type_hybrid">Hibdridowé</string>
<string name="login_token_hint">Token</string>
<string name="login_pin_hint">PIN</string>
<string name="login_symbol_hint">Symbol</string>
<string name="login_symbol_placeholder">Np. \"lodz\" czë \"powiatjaroslawski\"</string>
<string name="login_sign_in">Zalogùj</string>
<string name="login_invalid_password">Parola je za krótkô</string>
<string name="login_incorrect_password_default">Dane logòwaniô są niépasowné</string>
<string name="login_incorrect_password">%1$s. Ùgwësni sã, że wëbrôno pòprawną òdmianã dzénnika UONET+ niżi</string>
<string name="login_invalid_pin">Niepasowny PIN</string>
<string name="login_invalid_token">Niepasowny token</string>
<string name="login_expired_token">Token wëgôsł</string>
<string name="login_invalid_email">Niepasowny adresa e-mail</string>
<string name="login_invalid_login">Ùżij loginu miast adresu e-mail</string>
<string name="login_invalid_custom_email">Ùżij loginu abò adresu e-mail w @%1$s</string>
<string name="login_invalid_domain_suffix">Niepasowny sufiks domenë</string>
<string name="login_invalid_symbol">Niepasowny symbòl. Żelë nié mòżesz gò nalézc, proszã skòntaktowac sã z szkòłą</string>
<string name="login_invalid_symbol_definitely">Nie szkaluj! żelë nié mòżesz nalézc symbòlu, skòntaktuj sã z szkòłą</string>
<string name="login_incorrect_symbol">Ùczeń nie nalazłi. Sprôwdzë pòprawnosc symbòlu i wëbróny òdmianë dzénnika UONET+</string>
<string name="login_duplicate_student">Wëbróny ùczeń je ju zalogòwóny</string>
<string name="login_symbol_helper">Symbòl mòżna nalézc na starnie dzénnika w&#160;<b>Ùczeń</b>&#160;<b>Dostãp mòbilny</b>&#160;<b>Wëgeneruj kòd dostãpù </b>.\n\nÙgwësni sã, że ùstôwił jes pasowną òdmianã dzénnika w pòlu <b>Òdmiana dzénnika UONET+</b> na pierszim ekranie logòwania</string>
<string name="login_select_student">Wëbierzë ùczniów do zalogòwaniô w aplikacëje</string>
<string name="login_advanced">Jiné òpcëje</string>
<string name="login_advanced_warning_mobile_api">W tim tribie nie dzejô szczestlëwi numerk, ùczeń na tle klasë, pòdsumòwanié frekwencëje, ùsprôwiedliwianié nieòbecnoscë, zajmë zrealizowóné, jimfòrmacëje ò szkòle i pòzdrzatk listë zarejestrowónëch ùrządzeniów</string>
<string name="login_advanced_warning_scraper">Nen trib wëswietlô ne samé dane, chtërne są widoczné na jinternetowi starnie dzénnika</string>
<string name="login_advanced_warning_hybrid">Sparłãczënié nôlepszich znanków dwùch òstatnich tribów. Dzejô chùdzy jak scraper i ùgwësniô Fùnkcëje niédostãpné w tribie Mòbilné API, Je to w eksperimentalnym stadium</string>
<string name="login_privacy_policy">Pòlitëka priwatnoscë</string>
<string name="login_contact_header">Tôkel z logòwanim? Napiszë do naju!</string>
<string name="login_contact_email">E-mail</string>
<string name="login_contact_discord">Discord</string>
<string name="login_email_intent_title">Wëslë e-mail</string>
<string name="login_recover_warning">Ùgwësni sã, że òsta wëbrónô òdpòwiedniô òdmiana dzénnika UONET+!</string>
<string name="login_recover_button">Resetuj parolã</string>
<string name="login_recover_title">Przëwarcë swòje kònto</string>
<string name="login_recover">Przëwarcë</string>
<string name="login_signed_in">Ùczeń je ju zalogòwóny</string>
<string name="login_host_standard">Sztandardowô</string>
<string name="login_other_search_locations">Jinô lokalizacëjô wësznëkrowaniô</string>
<string name="login_no_active_student">Nie nalazło aktiwnëch ùczniów</string>
<string name="login_symbol_enter">Wprowadzë jiny symbòl</string>
<string name="login_support_title">Zëskôj pòmòc</string>
<string name="login_support_school_hint">Fùl pòzwa szkòłë z gardã (Wëmôgô)</string>
<string name="login_support_school_placeholder">Np. ZSTiO Jarosław abò SP nr 99 w Łodzi</string>
<string name="login_support_school_invalid">Wprowadzë pòprawną pòzwã szkòłë</string>
<string name="login_support_additional_hint">Dodôtkòwé jinfòrmacëje pò Pòlskù (Òpcjonalno)</string>
<string name="login_support_additional_placeholder">Np. \"Òstatnio zmienił jem szkòłã i…\" abò \"Jô jem rodzëcã i nié widzã drëdżégò dzecka…\"</string>
<string name="login_support_submit">Wëslë</string>
<!--Notifications-->
<string name="notifications_header_title">Włączë wiadła</string>
<string name="notifications_header_description">Włączë wiadła, abë nie zabôczëc wiadów òd szkólnëch abò nowi taksë</string>
<string name="notifications_skip">Pòmiń</string>
<string name="notifications_enable">Włączë</string>
<!--Main-->
<string name="main_account_picker">Czerownik kòntów</string>
<string name="main_log_in">Zalogùj sã</string>
<string name="main_session_expired">Sesjô zgasłô</string>
<string name="main_session_relogin">Sesja zgasłô, zalogùj sã znôwa</string>
<string name="main_expired_credentials_title">Parola zgasłô abò òsta zmieniona</string>
<string name="main_expired_credentials_description">Parola do twòjégò kònta zgasłô abò òsta zmieniona. Mùszisz zalogòwac sã znôwa do Wùlkanowégò</string>
<string name="main_support_title">Wspiarcé aplikacëje</string>
<string name="main_support_description">Widzy cë sã ta aplikacëjô? Wësprzë ji rozwój pòprzez włączenié nieszkòdzącëch reklamów, chtërne mòżesz wëłączëc w kôżdim mòmence</string>
<string name="main_support_positive">Włączë reklamë</string>
<!--Grade-->
<string name="grade_header">Taksa</string>
<string name="grade_semester">Semestr %d</string>
<string name="grade_switch_semester">Zmieni semestr</string>
<string name="grade_no_items">Felënk taksów</string>
<string name="grade_weight">Wôga</string>
<string name="grade_weight_value">Wôga: %s</string>
<string name="grade_comment">Kòmentôrz</string>
<string name="grade_number_new_items">Jilosc nowich taksów: %1$d</string>
<string name="grade_average">Strzédnô: %1$.2f</string>
<string name="grade_average_year">Rocznô: %1$.2f</string>
<string name="grade_points_sum">Pónktë: %s</string>
<string name="grade_no_average">Felënk strzédny</string>
<string name="grade_summary_average_semester">Strzédnô semestru</string>
<string name="grade_summary_average_year">Strzédnô rocznô</string>
<string name="grade_summary_points">Jilosc pónktów</string>
<string name="grade_summary_final_grade">Kùńcowô taksa</string>
<string name="grade_summary_predicted_grade">Spòdzónô taksa</string>
<string name="grade_summary_descriptive">Òpisowô taksa</string>
<string name="grade_summary_calculated_average">Òbliczonô strzédnô semestru</string>
<string name="grade_summary_calculated_average_annual">Òbliczonô rocznô strzédna</string>
<string name="grade_summary_calculated_average_help_dialog_title">Jak dzejô òbliczonô strzédnô?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Òbrechòwiwónô strzédnô to je aritmeticznô strzédnô jakô je òbrechòwiwónô ze strzédnëch z apartnëch przibiorów. Pòzwôlô na doznanié sã przëblëżony kùńcowi strzédny. Òna je òbrechòwiwónô na ôrt wëbróny przez brëkòwnika w nastawach aplikacje. Bédëjemë wëbrac pasowną òpcją, kò òbrechòwiwanié szkòłowëch strzédnëch mòże sã jinaczëc. Żelë wasza szkòła pòdôwô strzédné z przibiorów na starnie Vulcan, aplikacjô je scygô a sama nick nie òbrechòwiwô. To mòże to zmienic przez wëmùszenié òbrechòwiwaniégò w nastawach aplikacje.\n\n<b>Strzédnô taksów leno z wëbrónégò semestru</b>:\n1. Òbrechòwiwanié wôżony strzédny dlô kòżdégò przibioru w dónym semestrze\n2.Dodôwanié òbrechòwónëch strzédnëch\n3. Òbrëchòwiwanié aritmeticzny strzédny ze strzédnëch zesadzonëch razã\n\n<b>Strzédnô ze strzédnëch z òbùch semestrów</b>:\n1.Òbrechòwiwanié wôżony strzédny dlô kòżdégò przibioru w 1. i 2. semestrze\n2. Òbrechòwiwanié aritmeticzny strzédny ze strzédnëch òbrechòwónëch dlô kòżdégò przëbioru na semester 1. i 2.\n3. Dodôwanié òbrechòwónëch strzédnëch\n4. Òbrëchòwiwanié aritmeticzny strzédny ze strzédnëch zesadzonëch razã\n\n<b>Strzédnô taksów z całégò rokù:</b>\n1. Òbrechòwiwanié wôżony strzédny w rocznym pòzdrzatkù dlô kòżdégò przibioru. Kùńcowô strzédnô w 1. semestrze sã nie rechùje. \n2. Dodôwanié òbrechòwónëch strzédnëch\n3. Òbrëchòwiwanié aritmeticzny strzédny ze strzédnëch zesadzonëch razã</string>
<string name="grade_summary_final_average_help_dialog_title">Jak fónksnéruje kùńcowô strzédna?</string>
<string name="grade_summary_final_average_help_dialog_message">Kùńcową strzédną je strzédnô aritmetëcznô òbliczonô na spòdlim wszëtczich òbecno dostãpnëch taksów kùńcowëch w danym semestrze.\n\nSchemat òbliczeniów skłôdô sã z nôstãpùjącëch kroków:\n1. Sumòwanié kùńcowëch taksów wpisanëch przez szkólnëch\n2. Dzélenié przez lëczbã zajmów, z chtërnëch taksë òstałë ju wstôwioné</string>
<string name="grade_summary_final_average">Lùńcowô strzédna</string>
<string name="grade_summary_from_subjects">z %1$d na %2$d zajmów</string>
<string name="grade_menu_summary">Pòdsëmòwanié</string>
<string name="grade_menu_statistics">Klasa</string>
<string name="grade_menu_read">Zamérkô jakò przeczëtóné</string>
<string name="grade_statistics_partial">Cawné</string>
<string name="grade_statistics_semester">Semestr</string>
<string name="grade_statistics_points">Pónktë</string>
<string name="grade_statistics_legend">Legenda</string>
<string name="grade_statistics_class_average">Strzédna klasë: %1$s</string>
<string name="grade_statistics_student_average">Twòja strzédnô: %1$s</string>
<string name="grade_statistics_student_grade">Twòja taksa: %1$s</string>
<string name="grade_statistics_average_class">Klasa</string>
<string name="grade_statistics_average_student">Ùczeń</string>
<plurals name="grade_number_item">
<item quantity="one">%d taksa</item>
<item quantity="few">%d taksë</item>
<item quantity="other">%d taksów</item>
</plurals>
<plurals name="grade_new_items">
<item quantity="one">Nowô taksa</item>
<item quantity="few">Nowé taksë</item>
<item quantity="other">Nowé taksë</item>
</plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">Nowô spòdzónô taksa</item>
<item quantity="few">Nowé spòdzóné taksë</item>
<item quantity="other">Nowé spòdzóné taksë</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">Nowô kùńcowô taksa</item>
<item quantity="few">Nowé kùńcowé taksë</item>
<item quantity="other">Nowé kùńcowé taksë</item>
</plurals>
<plurals name="grade_new_items_descriptive">
<item quantity="one">New descriptive grade</item>
<item quantity="few">New descriptive grades</item>
<item quantity="other">New descriptive grades</item>
</plurals>
<plurals name="grade_notify_new_items">
<item quantity="one">You received %1$d grade</item>
<item quantity="few">You received %1$d grades</item>
<item quantity="other">You received %1$d grades</item>
</plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">You received %1$d predicted grade</item>
<item quantity="few">You received %1$d predicted grades</item>
<item quantity="other">You received %1$d predicted grades</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">You received %1$d final grade</item>
<item quantity="few">You received %1$d final grades</item>
<item quantity="other">You received %1$d final grades</item>
</plurals>
<plurals name="grade_notify_new_items_descriptive">
<item quantity="one">You received %1$d descriptive grade</item>
<item quantity="few">You received %1$d descriptive grades</item>
<item quantity="other">You received %1$d descriptive grades</item>
</plurals>
<!--Timetable-->
<string name="timetable_lesson">Ùczba</string>
<string name="timetable_additional_lesson">Dodôtkòwô ùczba</string>
<string name="timetable_room">Zala</string>
<string name="timetable_group">Karno</string>
<string name="timetable_time">Gòdzënë</string>
<string name="timetable_changes">Zmianë</string>
<string name="timetable_no_items">Felënk ùczbów dzysô</string>
<string name="timetable_minutes">%s min</string>
<string name="timetable_seconds">%s sec</string>
<string name="timetable_time_left">%1$s left</string>
<string name="timetable_time_until">in %1$s</string>
<string name="timetable_finished">Finished</string>
<string name="timetable_now">Now: %s</string>
<string name="timetable_next">Next: %s</string>
<string name="timetable_later">Later: %s</string>
<string name="timetable_notify_lesson">%1$s lesson %2$d - %3$s</string>
<string name="timetable_notify_change_room">Change of room from %1$s to %2$s</string>
<string name="timetable_notify_change_teacher">Change of teacher from %1$s to %2$s</string>
<string name="timetable_notify_change_subject">Change of subject from %1$s to %2$s</string>
<plurals name="timetable_no_lesson">
<item quantity="one">No lesson</item>
<item quantity="few">No lessons</item>
<item quantity="other">No lessons</item>
</plurals>
<plurals name="timetable_notify_new_items_title">
<item quantity="one">Timetable change</item>
<item quantity="few">Timetable changes</item>
<item quantity="other">Timetable changes</item>
</plurals>
<plurals name="timetable_notify_new_items">
<item quantity="one">%1$s - %2$d change in timetable</item>
<item quantity="few">%1$s - %2$d changes in timetable</item>
<item quantity="other">%1$s - %2$d changes in timetable</item>
</plurals>
<plurals name="timetable_notify_new_items_group">
<item quantity="one">%1$d change in timetable</item>
<item quantity="few">%1$d changes in timetable</item>
<item quantity="other">%1$d changes in timetable</item>
</plurals>
<plurals name="timetable_number_item">
<item quantity="one">%d change</item>
<item quantity="few">%d changes</item>
<item quantity="other">%d changes</item>
</plurals>
<!--Completed lessons-->
<string name="completed_lessons_title">Completed lessons</string>
<string name="completed_lessons_button">Pòkôżë skùńczoné ùczbë</string>
<string name="completed_lessons_no_items">No info about completed lessons</string>
<string name="completed_lessons_topic">Topic</string>
<string name="completed_lessons_absence">Niebëtnosc</string>
<string name="completed_lessons_resources">Zôsóbczi</string>
<!--Additional lessons-->
<string name="additional_lessons_title">Dodatkòwé ùczbë</string>
<string name="additional_lessons_button">Pòkôżë dodatkòwé ùczbë</string>
<string name="additional_lessons_no_items">Felënk jinfòrmacëjów ò dodôtkòwëch ùczbach</string>
<string name="additional_lessons_add">Nowô ùczba</string>
<string name="additional_lessons_add_title">Nowô dodatkòwô ùczba</string>
<string name="additional_lessons_add_success">Dodatkòwa ùczba òsta dodónô z sukcesã</string>
<string name="additional_lessons_delete_success">Additional lesson deleted successfully</string>
<string name="additional_lessons_repeat">Repeat weekly</string>
<string name="additional_lessons_delete_title">Delete additional lesson</string>
<string name="additional_lessons_delete_one">Just this lesson</string>
<string name="additional_lessons_delete_series">All in the series</string>
<string name="additional_lessons_start">Start time</string>
<string name="additional_lessons_end">End time</string>
<string name="additional_lessons_end_time_error">End time must be greater than start time</string>
<!--Attendance-->
<string name="attendance_summary_button">Attendance summary</string>
<string name="attendance_calculator_button">Attendance calculator</string>
<string name="attendance_calculator_summary_balance_positive"><b>%1$d</b> over 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_values">%1$d/%2$d presences</string>
<string name="attendance_calculator_summary_values_empty">No attendances recorded</string>
<string name="attendance_absence_school">Absent for school reasons</string>
<string name="attendance_absence_excused">Excused absence</string>
<string name="attendance_absence_unexcused">Unexcused absence</string>
<string name="attendance_exemption">Exemption</string>
<string name="attendance_excused_lateness">Excused lateness</string>
<string name="attendance_unexcused_lateness">Unexcused lateness</string>
<string name="attendance_present">Present</string>
<string name="attendance_deleted">Deleted</string>
<string name="attendance_unknown">Unknown</string>
<string name="attendance_number">Number of lesson</string>
<string name="attendance_no_items">No entries</string>
<string name="attendance_excuse_dialog_reason">Absence reason (optional)</string>
<string name="attendance_excuse_dialog_submit">Send</string>
<string name="attendance_excuse_success">Absence excuse request sent successfully!</string>
<string name="attendance_excuse_no_selection">You must select at least one absence!</string>
<string name="attendance_excuse_title">Excuse</string>
<plurals name="attendance_notify_new_items_title">
<item quantity="one">New attendance</item>
<item quantity="few">New attendance</item>
<item quantity="other">New attendance</item>
</plurals>
<plurals name="attendance_notify_new_items">
<item quantity="one">%1$d new attendance</item>
<item quantity="few">%1$d attendance</item>
<item quantity="other">%1$d attendance</item>
</plurals>
<plurals name="attendance_number_item">
<item quantity="one">%d attendance</item>
<item quantity="few">%d attendance</item>
<item quantity="other">%d attendance</item>
</plurals>
<!--Attendance summary-->
<string name="attendance_summary_total">Total</string>
<!--Exam-->
<string name="exam_no_items">No exams this week</string>
<string name="exam_type">Type</string>
<string name="exam_entry_date">Entry date</string>
<plurals name="exam_notify_new_item_title">
<item quantity="one">New exam</item>
<item quantity="few">New exams</item>
<item quantity="other">New exams</item>
</plurals>
<plurals name="exam_notify_new_item_content">
<item quantity="one">%d new exam</item>
<item quantity="few">%d new exams</item>
<item quantity="other">%d new exams</item>
</plurals>
<plurals name="exam_number_item">
<item quantity="one">%d exam</item>
<item quantity="few">%d exams</item>
<item quantity="other">%d exams</item>
</plurals>
<!--Message-->
<string name="message_inbox">Inbox</string>
<string name="message_sent">Sent</string>
<string name="message_trash">Trash</string>
<string name="message_no_subject">(no subject)</string>
<string name="message_no_items">No messages</string>
<string name="message_from">From:</string>
<string name="message_to">To:</string>
<string name="message_date">Date: %1$s</string>
<string name="message_reply">Reply</string>
<string name="message_forward">Forward</string>
<string name="message_select_all">Select all</string>
<string name="message_unselect_all">Unselect all</string>
<string name="message_restore_from_trash">Restore from trash</string>
<string name="message_move_to_trash">Move to trash</string>
<string name="message_delete_forever">Delete permanently</string>
<string name="message_restore_success">Message restored successfully</string>
<string name="message_delete_success">Message deleted successfully</string>
<string name="message_mailbox_type_student">student</string>
<string name="message_mailbox_type_parent">parent</string>
<string name="message_mailbox_type_guardian">guardian</string>
<string name="message_mailbox_type_employee">employee</string>
<string name="message_share">Share</string>
<string name="message_print">Print</string>
<string name="message_subject">Subject</string>
<string name="message_content">Content</string>
<string name="message_send_successful">Message sent successfully</string>
<string name="message_not_exists">Message does not exist</string>
<string name="message_required_recipients">You need to choose at least 1 recipient</string>
<string name="message_content_min_length">The message content must be at least 3 characters</string>
<string name="message_chip_all_mailboxes">All mailboxes</string>
<string name="message_chip_only_unread">Only unread</string>
<string name="message_chip_only_with_attachments">Only with attachments</string>
<string name="message_read">Read: %s</string>
<string name="message_read_by">Read by: %1$d of %2$d people</string>
<plurals name="message_number_item">
<item quantity="one">%1$d message</item>
<item quantity="few">%1$d messages</item>
<item quantity="other">%1$d messages</item>
</plurals>
<plurals name="message_new_items">
<item quantity="one">New message</item>
<item quantity="few">New messages</item>
<item quantity="other">New messages</item>
</plurals>
<string name="message_restore_dialog">Do you want to restore draft message?</string>
<string name="message_restore_dialog_with_recipients">Do you want to restore draft message with recipients: %s?</string>
<plurals name="message_notify_new_items">
<item quantity="one">You received %1$d message</item>
<item quantity="few">You received %1$d messages</item>
<item quantity="other">You received %1$d messages</item>
</plurals>
<plurals name="message_selected_messages_count">
<item quantity="one">%1$d selected</item>
<item quantity="few">%1$d selected</item>
<item quantity="other">%1$d selected</item>
</plurals>
<string name="message_messages_deleted">Messages deleted</string>
<string name="message_messages_restored">Messages restored</string>
<string name="message_mailbox_chooser_title">Choose mailbox</string>
<string name="message_incognito_mode_on">Incognito mode is on</string>
<string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
<!--Note-->
<string name="note_no_items">No info about notes</string>
<string name="note_points">Points</string>
<plurals name="note_number_item">
<item quantity="one">%d note</item>
<item quantity="few">%d notes</item>
<item quantity="other">%d notes</item>
</plurals>
<plurals name="note_new_items">
<item quantity="one">New note</item>
<item quantity="few">New notes</item>
<item quantity="other">New notes</item>
</plurals>
<plurals name="note_notify_new_items">
<item quantity="one">You received %1$d note</item>
<item quantity="few">You received %1$d notes</item>
<item quantity="other">You received %1$d notes</item>
</plurals>
<!--Praise-->
<plurals name="praise_number_item">
<item quantity="one">%d praise</item>
<item quantity="few">%d praises</item>
<item quantity="other">%d praises</item>
</plurals>
<plurals name="praise_new_items">
<item quantity="one">New praise</item>
<item quantity="few">New praises</item>
<item quantity="other">New praises</item>
</plurals>
<plurals name="praise_notify_new_items">
<item quantity="one">You received %1$d praise</item>
<item quantity="few">You received %1$d praises</item>
<item quantity="other">You received %1$d praises</item>
</plurals>
<!--Neutral notes-->
<plurals name="neutral_note_number_item">
<item quantity="one">%d neutral note</item>
<item quantity="few">%d neutral notes</item>
<item quantity="other">%d neutral notes</item>
</plurals>
<plurals name="neutral_note_new_items">
<item quantity="one">New neutral note</item>
<item quantity="few">New neutral notes</item>
<item quantity="other">New neutral notes</item>
</plurals>
<plurals name="neutral_note_notify_new_items">
<item quantity="one">You received %1$d neutral note</item>
<item quantity="few">You received %1$d neutral notes</item>
<item quantity="other">You received %1$d neutral notes</item>
</plurals>
<!--Homework-->
<string name="homework_no_items">No info about homework</string>
<string name="homework_mark_as_done">Mark as done</string>
<string name="homework_mark_as_undone">Mark as undone</string>
<string name="homework_add">Add homework</string>
<string name="homework_add_success">Homework added successfully</string>
<string name="homework_delete_success">Homework deleted successfully</string>
<string name="homework_attachments">Attachments</string>
<plurals name="homework_notify_new_item_title">
<item quantity="one">New homework</item>
<item quantity="few">New homework</item>
<item quantity="other">New homework</item>
</plurals>
<plurals name="homework_notify_new_item_content">
<item quantity="one">You received %d new homework</item>
<item quantity="few">You received %d new homework</item>
<item quantity="other">You received %d new homework</item>
</plurals>
<plurals name="homework_number_item">
<item quantity="one">%d homework</item>
<item quantity="few">%d homework</item>
<item quantity="other">%d homework</item>
</plurals>
<!--Lucky number-->
<string name="lucky_number_title">Lucky number</string>
<string name="lucky_number_header">Today\'s lucky number is</string>
<string name="lucky_number_empty">No info about the lucky number</string>
<string name="lucky_number_notify_new_item_title">Lucky number for today</string>
<string name="lucky_number_notify_new_item">Today\'s lucky number is: %s</string>
<string name="lucky_number_history_button">Show history</string>
<!--Lucky number history-->
<string name="lucky_number_history_title">Lucky number history</string>
<string name="lucky_number_history_empty">No info about lucky numbers</string>
<!--Mobile devices-->
<string name="mobile_devices_title">Mobile devices</string>
<string name="mobile_devices_no_items">No devices</string>
<string name="mobile_devices_unregister">Deregister</string>
<string name="mobile_device_removed">Device removed</string>
<string name="mobile_device_qr">QR code</string>
<string name="mobile_device_token">Token</string>
<string name="mobile_device_symbol">Symbol</string>
<string name="mobile_device_pin">PIN</string>
<!--School and teachers-->
<string name="schoolandteachers_title">School and teachers</string>
<!--School-->
<string name="school_title">School</string>
<string name="school_no_info">No info about school</string>
<string name="school_name">School name</string>
<string name="school_address">School address</string>
<string name="school_telephone">Telephone</string>
<string name="school_headmaster">Name of headmaster</string>
<string name="school_pedagogue">Name of pedagogue</string>
<string name="school_address_button">Show on map</string>
<string name="school_telephone_button">Call</string>
<!--Teacher-->
<string name="teachers_title">Teachers</string>
<string name="teacher_no_items">No info about teachers</string>
<string name="teacher_no_subject">No subject</string>
<!--Conference-->
<string name="conferences_title">Conferences</string>
<string name="conference_no_items">No info about conferences</string>
<plurals name="conference_number_item">
<item quantity="one">%d conference</item>
<item quantity="few">%d conferences</item>
<item quantity="other">%d conferences</item>
</plurals>
<plurals name="conference_notify_new_item_title">
<item quantity="one">New conference</item>
<item quantity="few">New conferences</item>
<item quantity="other">New conferences</item>
</plurals>
<plurals name="conference_notify_new_items">
<item quantity="one">You have %1$d new conference</item>
<item quantity="few">You have %1$d new conferences</item>
<item quantity="other">You have %1$d new conferences</item>
</plurals>
<string name="conferences_present">Present at conference</string>
<string name="conference_agenda">Agenda</string>
<string name="conference_place">Place</string>
<string name="conference_topic">Topic</string>
<!--Director information-->
<string name="school_announcement_title">School announcements</string>
<string name="school_announcement_no_items">No school announcements</string>
<plurals name="school_announcement_number_item">
<item quantity="one">%d school announcement</item>
<item quantity="few">%d school announcements</item>
<item quantity="other">%d school announcements</item>
</plurals>
<plurals name="school_announcement_notify_new_item_title">
<item quantity="one">New school announcement</item>
<item quantity="few">New school announcements</item>
<item quantity="other">New school announcements</item>
</plurals>
<plurals name="school_announcement_notify_new_items">
<item quantity="one">You have %1$d new school announcement</item>
<item quantity="few">You have %1$d new school announcements</item>
<item quantity="other">You have %1$d new school announcements</item>
</plurals>
<!--Account-->
<string name="account_add_new">Add account</string>
<string name="account_logout">Logout</string>
<string name="account_confirm">Do you want to log out this student?</string>
<string name="account_logout_student">Student logout</string>
<string name="account_type_student">Student account</string>
<string name="account_type_parent">Parent account</string>
<string name="account_details_edit">Edit data</string>
<string name="account_quick_manager">Accounts manager</string>
<string name="account_select_student">Select student</string>
<string name="account_family">Family</string>
<string name="account_contact">Contact</string>
<string name="account_address">Residence details</string>
<string name="account_personal_data">Personal information</string>
<!--About-->
<string name="about_version">App version</string>
<string name="about_contributor">Contributors</string>
<string name="about_contributor_summary">List of Wulkanowy developers</string>
<string name="about_feedback">Report a bug</string>
<string name="about_feedback_summary">Send a bug report via e-mail</string>
<string name="about_faq">FAQ</string>
<string name="about_faq_summary">Read Frequently Asked Questions</string>
<string name="about_discord">Discord server</string>
<string name="about_discord_summary">Join the Wulkanowy community</string>
<string name="about_facebook">Facebook fanpage</string>
<string name="about_twitter">Twitter page</string>
<string name="about_twitter_summary">Follow us on twitter</string>
<string name="about_facebook_summary">Like our facebook fanpage</string>
<string name="about_privacy">Privacy policy</string>
<string name="about_privacy_summary">Rules for collecting personal data</string>
<string name="about_system">System settings</string>
<string name="about_system_summary">Open system settings</string>
<string name="about_homepage">Homepage</string>
<string name="about_homepage_summary">Visit the website and help develop the application</string>
<string name="about_licenses">Licenses</string>
<string name="about_licenses_summary">Licenses of libraries used in the application</string>
<!--Licenses-->
<string name="license_dialog_title">License</string>
<!--Contributor-->
<string name="contributor_avatar_description">Avatar</string>
<string name="contributor_see_more">See more on GitHub</string>
<!--Student info-->
<string name="student_info_empty">No info about student or student family</string>
<string name="student_info_first_name">Name</string>
<string name="student_info_second_name">Second name</string>
<string name="student_info_gender">Gender</string>
<string name="student_info_polish_citizenship">Polish citizenship</string>
<string name="student_info_family_name">Family name</string>
<string name="student_info_parents_name">Mother\'s and father\'s names</string>
<string name="student_info_phone">Phone</string>
<string name="student_info_cellphone">Cellphone</string>
<string name="student_info_email">E-mail</string>
<string name="student_info_address">Address of residence</string>
<string name="student_info_registered_address">Address of registration</string>
<string name="student_info_correspondence_address">Correspondence address</string>
<string name="student_info_full_name">Surname and first name</string>
<string name="student_info_kinship">Degree of kinship</string>
<string name="student_info_guardian_address">Address</string>
<string name="student_info_phones">Phones</string>
<string name="student_info_male">Male</string>
<string name="student_info_female">Female</string>
<string name="student_info_last_name">Last name</string>
<string name="student_info_guardian">Guardian</string>
<!--Account edit-->
<string name="account_edit_nick_hint">Nick</string>
<string name="account_edit_header">Add nick</string>
<string name="account_edit_avatar_title">Choose avatar color</string>
<!--Log viewer-->
<string name="logviewer_share">Share logs</string>
<string name="logviewer_refresh">Refresh</string>
<!--Dashboard-->
<string name="dashboard_timetable_title">Lessons</string>
<string name="dashboard_timetable_title_tomorrow">(Tomorrow)</string>
<string name="dashboard_timetable_title_today_and_tomorrow">(Today and tomorrow)</string>
<string name="dashboard_timetable_first_lesson_title_moment">In a moment:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Soon:</string>
<string name="dashboard_timetable_first_lesson_title_first">First:</string>
<string name="dashboard_timetable_first_lesson_title_now">Now:</string>
<string name="dashboard_timetable_second_lesson_value_end">End of lessons</string>
<string name="dashboard_timetable_second_lessons_title">Next:</string>
<string name="dashboard_timetable_third_title">Later:</string>
<plurals name="dashboard_timetable_third_value">
<item quantity="one">%1$d more lesson</item>
<item quantity="few">%1$d more lessons</item>
<item quantity="other">%1$d more lessons</item>
</plurals>
<string name="dashboard_timetable_third_time">until %1$s</string>
<string name="dashboard_timetable_no_lessons">No upcoming lessons</string>
<string name="dashboard_timetable_error">An error occurred while loading the lessons</string>
<string name="dashboard_homework_title">Homework</string>
<string name="dashboard_homework_no_homework">No homework to do</string>
<string name="dashboard_homework_error">An error occurred while loading the homework</string>
<plurals name="dashboard_homework_more">
<item quantity="one">%1$d more homework</item>
<item quantity="few">%1$d more homework</item>
<item quantity="other">%1$d more homework</item>
</plurals>
<string name="dashboard_homework_time">due %1$s</string>
<string name="dashboard_grade_title">Last grades</string>
<string name="dashboard_grade_no_grade">No new grades</string>
<string name="dashboard_grade_error">An error occurred while loading the grades</string>
<string name="dashboard_announcements_title">School announcements</string>
<string name="dashboard_announcements_no_announcements">No current announcements</string>
<string name="dashboard_announcements_error">An error occurred while loading the announcements</string>
<plurals name="dashboard_announcements_more">
<item quantity="one">%1$d more announcement</item>
<item quantity="few">%1$d more announcements</item>
<item quantity="other">%1$d more announcements</item>
</plurals>
<string name="dashboard_exams_title">Exams</string>
<string name="dashboard_exams_no_exams">No upcoming exams</string>
<string name="dashboard_exams_error">An error occurred while loading the exams</string>
<plurals name="dashboard_exams_more">
<item quantity="one">%1$d more exam</item>
<item quantity="few">%1$d more exams</item>
<item quantity="other">%1$d more exams</item>
</plurals>
<string name="dashboard_conferences_title">Conferences</string>
<string name="dashboard_conferences_no_conferences">No upcoming conferences</string>
<string name="dashboard_conferences_error">An error occurred while loading the conferences</string>
<plurals name="dashboard_conference_more">
<item quantity="one">%1$d more conference</item>
<item quantity="few">%1$d more conferences</item>
<item quantity="other">%1$d more conferences</item>
</plurals>
<string name="dashboard_horizontal_group_error">An error occurred while loading data</string>
<string name="dashboard_horizontal_group_no_data">None</string>
<!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string>
<!--Generic-->
<string name="all_content">Content</string>
<string name="all_retry">Retry</string>
<string name="all_description">Description</string>
<string name="all_no_description">No description</string>
<string name="all_teacher">Teacher</string>
<string name="all_date">Date</string>
<string name="all_entry_date">Entry date</string>
<string name="all_color">Color</string>
<string name="all_details">Details</string>
<string name="all_category">Category</string>
<string name="all_close">Close</string>
<string name="all_no_data">No data</string>
<string name="all_subject">Subject</string>
<string name="all_prev">Prev</string>
<string name="all_next">Next</string>
<string name="all_search">Search</string>
<string name="all_search_hint">Search…</string>
<string name="all_yes">Yes</string>
<string name="all_no">No</string>
<string name="all_save">Save</string>
<string name="all_title">Title</string>
<string name="all_add">Add</string>
<string name="all_copied">Copied</string>
<string name="all_undo">Undo</string>
<string name="all_change">Change</string>
<string name="all_add_to_calendar">Add to calendar</string>
<string name="all_cancel">Cancel</string>
<!--Timetable Widget-->
<string name="widget_timetable_no_items">No lessons</string>
<string name="widget_timetable_last_synchronization">Synchronized on %1$s at %2$s</string>
<string name="widget_timetable_theme_title">Choose theme</string>
<string name="widget_timetable_theme_light">Light</string>
<string name="widget_timetable_theme_dark">Dark</string>
<string name="widget_timetable_theme_system">System Theme</string>
<!--Preferences-->
<string name="pref_view_header">App</string>
<string name="pref_view_list">Default view</string>
<string name="pref_view_grade_average_mode">Calculated average options</string>
<string name="pref_view_grade_average_force_calc">Force average calculation by app</string>
<string name="pref_view_present">Show presence</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_app_theme">Theme</string>
<string name="pref_view_expand_grade">Grades expanding</string>
<string name="pref_view_timetable_show_groups">Show groups next to subjects</string>
<string name="pref_view_timetable_show_additional_lessons">Show additional lessons</string>
<string name="pref_view_timetable_show_gaps">Show empty tiles where there\'s no lesson</string>
<string name="pref_view_grade_statistics_list">Show chart list in class grades</string>
<string name="pref_view_subjects_without_grades">Show subjects without grades</string>
<string name="pref_view_grade_color_scheme">Grades color scheme</string>
<string name="pref_view_grade_sorting_mode">Subjects sorting</string>
<string name="pref_view_app_language">Language</string>
<string name="pref_view_menu_order_title">Menu configuration</string>
<string name="pref_view_menu_order_summary">Set the order of functions in the menu</string>
<string name="pref_notify_header">Notifications</string>
<string name="pref_notify_header_other">Other</string>
<string name="pref_notify_switch">Show notifications</string>
<string name="pref_notify_upcoming_lessons_switch">Show upcoming lesson notifications</string>
<string name="pref_notify_upcoming_lessons_persistent_switch">Make upcoming lesson notification persistent</string>
<string name="pref_notify_upcoming_lessons_persistent_summary">Turn off when notification is not showing in your watch/band</string>
<string name="pref_notify_open_system_settings">Open system notification settings</string>
<string name="pref_notify_fix_sync_issues">Fix synchronization &amp; notifications issues</string>
<string name="pref_notify_fix_sync_issues_message">Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.</string>
<string name="pref_notify_debug_switch">Show debug notifications</string>
<string name="pref_notify_disabled_summary">Synchronization is disabled</string>
<string name="pref_notify_notifications_piggyback_header">Official app notifications</string>
<string name="pref_notify_notifications_piggyback">Capture official app notifications</string>
<string name="pref_notify_notifications_piggyback_cancel_original">Remove official app notifications after capture</string>
<string name="pref_notification_piggyback_popup_title">Capture notifications</string>
<string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string>
<string name="pref_notification_exact_alarm_popup_title">Upcoming lesson notifications</string>
<string name="pref_notification_exact_alarm_popup_descriptions">You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.</string>
<string name="pref_notification_go_to_settings">Go to settings</string>
<string name="pref_services_header">Synchronization</string>
<string name="pref_services_switch">Automatic update</string>
<string name="pref_services_suspended">Suspended on holidays</string>
<string name="pref_services_interval">Updates interval</string>
<string name="pref_services_wifi">Wi-Fi only</string>
<string name="pref_services_force_sync">Sync now</string>
<string name="pref_services_message_sync_success">Synced!</string>
<string name="pref_services_message_sync_failed">Sync failed</string>
<string name="pref_services_sync_in_progress">Sync in progress</string>
<string name="pref_services_last_full_sync_date">Last full sync: %s</string>
<string name="pref_other_grade_modifier_plus">Value of the plus</string>
<string name="pref_other_grade_modifier_minus">Value of the minus</string>
<string name="pref_other_fill_message_content">Reply with message history</string>
<string name="pref_other_optional_arithmetic_average">Show arithmetic average when no weights provided</string>
<string name="pref_other_incognito_mode">Incognito mode</string>
<string name="pref_other_incognito_mode_summary">Do not inform about reading the message</string>
<string name="pref_ads_support_category_name">Support</string>
<string name="pref_ads_privacy_policy">Privacy Policy</string>
<string name="pref_ads_agreements">Agreements</string>
<string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Show ads in app</string>
<string name="pref_ads_support">Watch single ad to support project</string>
<string name="pref_ads_privacy_title">Consent to data processing</string>
<string name="pref_ads_privacy_description">To view an advertisement you must agree to the data processing terms of our Privacy Policy</string>
<string name="pref_ads_privacy_agree">Agree</string>
<string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_settings_advanced_title">Advanced</string>
<string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>
<string name="pref_settings_notifications_title">Notifications</string>
<string name="pref_settings_sync_title">Synchronization</string>
<string name="pref_settings_ads_title">Advertisements</string>
<string name="pref_grades_appearance_header">Grades</string>
<string name="pref_dashboard_appearance_header">Dashboard</string>
<string name="pref_dashboard_appearance_tiles_title">Tiles visibility</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_grades_advanced_header">Grades</string>
<string name="pref_counted_average_advanced_header">Calculated average</string>
<string name="pref_messages_advanced_header">Messages</string>
<string name="pref_appearance_category">Appearance &amp; Behavior</string>
<string name="pref_appearance_category_summary">Languages, themes, subjects sorting</string>
<string name="pref_notifications_category_summary">App notifications, fix problems</string>
<string name="pref_notifications_category">Notifications</string>
<string name="pref_sync_category">Synchronization</string>
<string name="pref_sync_category_summary">Automatic update, synchronization interval</string>
<string name="pref_advanced_category_summary">Plus and minus values, average calculation</string>
<string name="pref_advanced_category">Advanced</string>
<string name="pref_about_category_summary">App version, contributors, social portals</string>
<string name="pref_ads_category_summary">Displaying advertisements, project support</string>
<!--Notification Channels-->
<string name="channel_new_grades">New grades</string>
<string name="channel_new_homework">New homework</string>
<string name="channel_new_conference">New conferences</string>
<string name="channel_new_exam">New exams</string>
<string name="channel_lucky_number">Lucky number</string>
<string name="channel_new_message">New messages</string>
<string name="channel_new_notes">New notes</string>
<string name="channel_new_school_announcement">New school announcements</string>
<string name="channel_push">Push notifications</string>
<string name="channel_upcoming_lessons">Upcoming lessons</string>
<string name="channel_debug">Debug</string>
<string name="channel_change_timetable">Timetable change</string>
<string name="channel_new_attendance">New attendance</string>
<!--Colors-->
<string name="all_black">Black</string>
<string name="all_red">Red</string>
<string name="all_blue">Blue</string>
<string name="all_green">Green</string>
<string name="all_purple">Purple</string>
<string name="all_empty_color">No color</string>
<!--Update helper-->
<string name="update_download_started">Download of updates has started…</string>
<string name="update_download_success">An update has just been downloaded.</string>
<string name="update_download_success_button">Restart</string>
<string name="update_failed">Update failed! Wulkanowy may not function properly. Consider updating</string>
<!--Menu order-->
<string name="menu_order_confirm_title">Application restart</string>
<string name="menu_order_confirm_content">The application must restart for the changes to be saved</string>
<string name="menu_order_confirm_restart">Restart</string>
<!--Auth-->
<string name="auth_api_error">Authorization has been rejected. The data provided does not match the records in the secretary\'s office.</string>
<string name="auth_invalid_error">Invalid PESEL</string>
<string name="auth_pesel">PESEL</string>
<string name="auth_button">Authorize</string>
<string name="auth_success">Authorization completed successfully</string>
<string name="auth_title">Authorization</string>
<string name="auth_description">Dear Parent,&lt;br /&gt;&lt;br /&gt;To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student &lt;b&gt;%1$s&lt;/b&gt;. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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>
<!--Captcha-->
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
<string name="captcha_verified_message">Verified successfully</string>
<!--Panic mode-->
<string name="panic_mode_title">Emergency access</string>
<!--Errors-->
<string name="error_no_internet">No internet connection</string>
<string name="error_invalid_device_datetime">An error occurred. Check your device clock</string>
<string name="error_account_inactive">This account is inactive. Try logging in again</string>
<string name="error_timeout">Connection to register failed. Servers can be overloaded. Please try again later</string>
<string name="error_login_failed">Loading data failed. Please try again later</string>
<string name="error_password_invalid">Your password has expired or been changed. Please log in again</string>
<string name="error_password_change_required">Register password change required</string>
<string name="error_service_unavailable">Maintenance underway UONET + register. Try again later</string>
<string name="error_unknown_uonet">Unknown UONET + register error. Try again later</string>
<string name="error_unknown_app">Unknown application error. Please try again later</string>
<string name="error_cloudflare_captcha">Captcha verification required</string>
<string name="error_unknown">An unexpected error occurred</string>
<string name="error_feature_disabled">Feature disabled by your school</string>
<string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string>
<string name="error_field_required">This field is required</string>
<!-- Mute system -->
<string name="message_mute">Mute</string>
<string name="message_unmute">Unmute</string>
<string name="message_mute_success">You have muted this user</string>
<string name="message_unmute_success">You have unmuted this user</string>
</resources>

View File

@ -778,8 +778,6 @@
<string name="captcha_dialog_title">VULCAN\'s Website erfordert Überprüfung</string> <string name="captcha_dialog_title">VULCAN\'s Website erfordert Überprüfung</string>
<string name="captcha_dialog_description"><b>Warum sehe ich das?</b>\nDie Website des Registers, von der Wulkanowy Daten herunterlädt, zeigt denselben Bildschirm wie oben an, so dass Wulkanowy ihn ebenfalls anzeigen muss, um Daten von dieser Website herunterladen zu können. Es gibt keinen Ausweg</string> <string name="captcha_dialog_description"><b>Warum sehe ich das?</b>\nDie Website des Registers, von der Wulkanowy Daten herunterlädt, zeigt denselben Bildschirm wie oben an, so dass Wulkanowy ihn ebenfalls anzeigen muss, um Daten von dieser Website herunterladen zu können. Es gibt keinen Ausweg</string>
<string name="captcha_verified_message">Erfolgreich verifiziert</string> <string name="captcha_verified_message">Erfolgreich verifiziert</string>
<!--Panic mode-->
<string name="panic_mode_title">Notfallzugriff</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Keine Internetverbindung</string> <string name="error_no_internet">Keine Internetverbindung</string>
<string name="error_invalid_device_datetime">Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr</string> <string name="error_invalid_device_datetime">Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr</string>

View File

@ -872,8 +872,6 @@
<string name="captcha_dialog_title">Strona dziennika VULCAN wymaga weryfikacji</string> <string name="captcha_dialog_title">Strona dziennika VULCAN wymaga weryfikacji</string>
<string name="captcha_dialog_description"><b>Dlaczego to widzę?</b>\nStrona internetowa dziennika, z której Wulkanowy pobiera dane, wyświetla ten sam ekran jak powyżej, więc Wulkanowy musi również ją pokazać, aby móc pobrać dane z tej witryny. Nie da się tego obejść</string> <string name="captcha_dialog_description"><b>Dlaczego to widzę?</b>\nStrona internetowa dziennika, z której Wulkanowy pobiera dane, wyświetla ten sam ekran jak powyżej, więc Wulkanowy musi również ją pokazać, aby móc pobrać dane z tej witryny. Nie da się tego obejść</string>
<string name="captcha_verified_message">Pomyślnie zweryfikowano</string> <string name="captcha_verified_message">Pomyślnie zweryfikowano</string>
<!--Panic mode-->
<string name="panic_mode_title">Dostęp awaryjny</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Brak połączenia z internetem</string> <string name="error_no_internet">Brak połączenia z internetem</string>
<string name="error_invalid_device_datetime">Wystąpił błąd. Sprawdź poprawność daty w urządzeniu</string> <string name="error_invalid_device_datetime">Wystąpił błąd. Sprawdź poprawność daty w urządzeniu</string>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="sort_alphabetically">По алфавиту</string> <string name="sort_alphabetically">Alphabetically</string>
<string name="sort_by_date">По дате</string> <string name="sort_by_date">By date</string>
<string name="sort_by_average">По средней</string> <string name="sort_by_average">By average</string>
<string name="sort_by_attendance_percentage">Согласно проценту посещаемости</string> <string name="sort_by_attendance_percentage">By attendance percentage</string>
<string name="sort_by_subject_attendance_balance">Согласно балансу посещаемости уроков</string> <string name="sort_by_subject_attendance_balance">By subject attendance balance</string>
<string-array name="app_theme_entries" tools:ignore="InconsistentArrays"> <string-array name="app_theme_entries" tools:ignore="InconsistentArrays">
<item>Светлая</item> <item>Светлая</item>
<item>Тёмная</item> <item>Тёмная</item>
@ -52,14 +52,14 @@
<item>Средняя из оценок со всего года</item> <item>Средняя из оценок со всего года</item>
</string-array> </string-array>
<string-array name="timetable_show_gaps_entries"> <string-array name="timetable_show_gaps_entries">
<item>Не показывать</item> <item>Don\'t show</item>
<item>Только между уроками</item> <item>Only between lessons</item>
<item>Перед и между уроками</item> <item>Before and between lessons</item>
</string-array> </string-array>
<string-array name="timetable_show_additional_lessons_entries"> <string-array name="timetable_show_additional_lessons_entries">
<item>Не показывать</item> <item>Don\'t show</item>
<item>Показать в строке</item> <item>Show inline</item>
<item>Показать ниже обычных уроков</item> <item>Show below regular lessons</item>
</string-array> </string-array>
<string-array name="dashboard_tile_entries"> <string-array name="dashboard_tile_entries">
<item>Счастливый номер</item> <item>Счастливый номер</item>

View File

@ -13,7 +13,7 @@
<string name="logviewer_title">Просмотр журнала</string> <string name="logviewer_title">Просмотр журнала</string>
<string name="debug_title">Отладка</string> <string name="debug_title">Отладка</string>
<string name="notification_debug_title">Отладка уведомлений</string> <string name="notification_debug_title">Отладка уведомлений</string>
<string name="debug_cookies_clear">Очистить файлы cookie</string> <string name="debug_cookies_clear">Clear webview cookies</string>
<string name="contributors_title">Разработчики</string> <string name="contributors_title">Разработчики</string>
<string name="license_title">Лицензии</string> <string name="license_title">Лицензии</string>
<string name="message_title">Сообщения</string> <string name="message_title">Сообщения</string>
@ -38,14 +38,14 @@
<string name="login_login_pesel_email_hint">Логин, PESEL или электронная почта</string> <string name="login_login_pesel_email_hint">Логин, PESEL или электронная почта</string>
<string name="login_password_hint">Пароль</string> <string name="login_password_hint">Пароль</string>
<string name="login_host_hint">Тип дневника UONET+</string> <string name="login_host_hint">Тип дневника UONET+</string>
<string name="login_domain_suffix_hint">Пользовательский суффикс домена</string> <string name="login_domain_suffix_hint">Custom domain suffix</string>
<string name="login_type_api">Мобильный API</string> <string name="login_type_api">Мобильный API</string>
<string name="login_type_scrapper">Scraper</string> <string name="login_type_scrapper">Scraper</string>
<string name="login_type_hybrid">Hybrid</string> <string name="login_type_hybrid">Hybrid</string>
<string name="login_token_hint">Token</string> <string name="login_token_hint">Token</string>
<string name="login_pin_hint">PIN</string> <string name="login_pin_hint">PIN</string>
<string name="login_symbol_hint">Symbol</string> <string name="login_symbol_hint">Symbol</string>
<string name="login_symbol_placeholder">Например: \"lodz\" или \"powiatjaroslawski\"</string> <string name="login_symbol_placeholder">E.g. \"lodz\" or \"powiatjaroslawski\"</string>
<string name="login_sign_in">Войти</string> <string name="login_sign_in">Войти</string>
<string name="login_invalid_password">Пароль слишком короткий</string> <string name="login_invalid_password">Пароль слишком короткий</string>
<string name="login_incorrect_password_default">Данные для входа указаны неверно</string> <string name="login_incorrect_password_default">Данные для входа указаны неверно</string>
@ -56,8 +56,8 @@
<string name="login_invalid_email">Неверный e-mail</string> <string name="login_invalid_email">Неверный e-mail</string>
<string name="login_invalid_login">Используйте назначенный логин вместо e-mail</string> <string name="login_invalid_login">Используйте назначенный логин вместо e-mail</string>
<string name="login_invalid_custom_email">Используйте назначенный логин или email в @%1$s</string> <string name="login_invalid_custom_email">Используйте назначенный логин или email в @%1$s</string>
<string name="login_invalid_domain_suffix">Недопустимый суффикс домена</string> <string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Неверный символ. Если вы не можете найти символ, пожалуйста, свяжитесь со школой</string> <string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string>
<string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string> <string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string>
<string name="login_incorrect_symbol">Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+</string> <string name="login_incorrect_symbol">Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+</string>
<string name="login_duplicate_student">Данный ученик уже авторизован</string> <string name="login_duplicate_student">Данный ученик уже авторизован</string>
@ -73,7 +73,7 @@
<string name="login_contact_discord">Discord</string> <string name="login_contact_discord">Discord</string>
<string name="login_email_intent_title">Отправить письмо</string> <string name="login_email_intent_title">Отправить письмо</string>
<string name="login_recover_warning">Убедитесь, что вы выбрали правильный тип дневника UONET+</string> <string name="login_recover_warning">Убедитесь, что вы выбрали правильный тип дневника UONET+</string>
<string name="login_recover_button">Сбросить пароль</string> <string name="login_recover_button">Reset password</string>
<string name="login_recover_title">Восстановите свой аккаунт</string> <string name="login_recover_title">Восстановите свой аккаунт</string>
<string name="login_recover">Восстановить</string> <string name="login_recover">Восстановить</string>
<string name="login_signed_in">Ученик уже авторизован</string> <string name="login_signed_in">Ученик уже авторизован</string>
@ -81,13 +81,13 @@
<string name="login_other_search_locations">Другие места поиска</string> <string name="login_other_search_locations">Другие места поиска</string>
<string name="login_no_active_student">Не найдено активных учеников</string> <string name="login_no_active_student">Не найдено активных учеников</string>
<string name="login_symbol_enter">Введите другой symbol</string> <string name="login_symbol_enter">Введите другой symbol</string>
<string name="login_support_title">Помощь</string> <string name="login_support_title">Get help</string>
<string name="login_support_school_hint">Полное название школы с городом (обязательно)</string> <string name="login_support_school_hint">Full school name with the town (required)</string>
<string name="login_support_school_placeholder">Например: ZSTiO Jarosław или SP nr 99 w Łodzi</string> <string name="login_support_school_placeholder">Np. ZSTiO Jarosław lub SP nr 99 w Łodzi</string>
<string name="login_support_school_invalid">Введите правильное название школы</string> <string name="login_support_school_invalid">Enter correct name of the school</string>
<string name="login_support_additional_hint">Дополнительная информация на польском языке (опционально)</string> <string name="login_support_additional_hint">Additional information in Polish (optional)</string>
<string name="login_support_additional_placeholder">Например: \"Ostatnio zmieniłem szkoł i…\" или \"Jestem rodzicem i nie widz drugiego dziecka…\"</string> <string name="login_support_additional_placeholder">Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"</string>
<string name="login_support_submit">Отправить</string> <string name="login_support_submit">Submit</string>
<!--Notifications--> <!--Notifications-->
<string name="notifications_header_title">Включить уведомления</string> <string name="notifications_header_title">Включить уведомления</string>
<string name="notifications_header_description">Включить уведомления, чтобы вы не пропустили сообщение от учителя или новую оценку</string> <string name="notifications_header_description">Включить уведомления, чтобы вы не пропустили сообщение от учителя или новую оценку</string>
@ -98,8 +98,8 @@
<string name="main_log_in">Войти</string> <string name="main_log_in">Войти</string>
<string name="main_session_expired">Сеанс истёк</string> <string name="main_session_expired">Сеанс истёк</string>
<string name="main_session_relogin">Сеанс истёк, авторизуйтесь снова</string> <string name="main_session_relogin">Сеанс истёк, авторизуйтесь снова</string>
<string name="main_expired_credentials_title">Срок действия пароля истек или был изменен</string> <string name="main_expired_credentials_title">Password has expired or been changed</string>
<string name="main_expired_credentials_description">Пароль вашей учетной записи устарел или был изменен. Вам нужно будет войти в Wulkanowy снова</string> <string name="main_expired_credentials_description">Your account password has expired or been changed. You will need to log in to Wulkanowy again</string>
<string name="main_support_title">Поддержка приложения</string> <string name="main_support_title">Поддержка приложения</string>
<string name="main_support_description">Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время</string> <string name="main_support_description">Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время</string>
<string name="main_support_positive">Включить рекламу</string> <string name="main_support_positive">Включить рекламу</string>
@ -113,16 +113,16 @@
<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_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">Средняя семестра</string> <string name="grade_summary_average_semester">Semester average</string>
<string name="grade_summary_average_year">Средняя годовой</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">Описательная оценка</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_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>
@ -167,10 +167,10 @@
<item quantity="other">Новые итоговые оценки</item> <item quantity="other">Новые итоговые оценки</item>
</plurals> </plurals>
<plurals name="grade_new_items_descriptive"> <plurals name="grade_new_items_descriptive">
<item quantity="one">Новая описательная оценка</item> <item quantity="one">New descriptive grade</item>
<item quantity="few">Новые описательные оценки</item> <item quantity="few">New descriptive grades</item>
<item quantity="many">Новые описательные оценки</item> <item quantity="many">New descriptive grades</item>
<item quantity="other">Новые описательные оценки</item> <item quantity="other">New descriptive grades</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items"> <plurals name="grade_notify_new_items">
<item quantity="one">Вы получили %1$d новую оценку</item> <item quantity="one">Вы получили %1$d новую оценку</item>
@ -191,14 +191,14 @@
<item quantity="other">Вы получили %1$d новых итоговые оценки</item> <item quantity="other">Вы получили %1$d новых итоговые оценки</item>
</plurals> </plurals>
<plurals name="grade_notify_new_items_descriptive"> <plurals name="grade_notify_new_items_descriptive">
<item quantity="one">Вы получили %1$d новую описательную оценку</item> <item quantity="one">You received %1$d descriptive grade</item>
<item quantity="few">Вы получили %1$d новых описательных оценок</item> <item quantity="few">You received %1$d descriptive grades</item>
<item quantity="many">Вы получили %1$d новых описательных оценок</item> <item quantity="many">You received %1$d descriptive grades</item>
<item quantity="other">Вы получили %1$d новых описательных оценок</item> <item quantity="other">You received %1$d descriptive grades</item>
</plurals> </plurals>
<!--Timetable--> <!--Timetable-->
<string name="timetable_lesson">Урок</string> <string name="timetable_lesson">Урок</string>
<string name="timetable_additional_lesson">Дополнительный урок</string> <string name="timetable_additional_lesson">Additional lesson</string>
<string name="timetable_room">Аудитория</string> <string name="timetable_room">Аудитория</string>
<string name="timetable_group">Группа</string> <string name="timetable_group">Группа</string>
<string name="timetable_time">Часы</string> <string name="timetable_time">Часы</string>
@ -217,10 +217,10 @@
<string name="timetable_notify_change_teacher">Учитель изменён с %1$s на %2$s</string> <string name="timetable_notify_change_teacher">Учитель изменён с %1$s на %2$s</string>
<string name="timetable_notify_change_subject">Тема изменена с %1$s на %2$s</string> <string name="timetable_notify_change_subject">Тема изменена с %1$s на %2$s</string>
<plurals name="timetable_no_lesson"> <plurals name="timetable_no_lesson">
<item quantity="one">Нет урока</item> <item quantity="one">No lesson</item>
<item quantity="few">Нет урока</item> <item quantity="few">No lessons</item>
<item quantity="many">Нет урока</item> <item quantity="many">No lessons</item>
<item quantity="other">Нет урока</item> <item quantity="other">No lessons</item>
</plurals> </plurals>
<plurals name="timetable_notify_new_items_title"> <plurals name="timetable_notify_new_items_title">
<item quantity="one">Изменение расписания</item> <item quantity="one">Изменение расписания</item>
@ -270,7 +270,7 @@
<string name="additional_lessons_end_time_error">Время окончания должно быть больше, чем время начала</string> <string name="additional_lessons_end_time_error">Время окончания должно быть больше, чем время начала</string>
<!--Attendance--> <!--Attendance-->
<string name="attendance_summary_button">Итоговая посещаемость</string> <string name="attendance_summary_button">Итоговая посещаемость</string>
<string name="attendance_calculator_button">Калькулятор посещаемости</string> <string name="attendance_calculator_button">Attendance calculator</string>
<string name="attendance_calculator_summary_balance_positive"><b>%1$d</b> over target</string> <string name="attendance_calculator_summary_balance_positive"><b>%1$d</b> over target</string>
<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>
@ -347,10 +347,10 @@
<string name="message_forward">Переслать</string> <string name="message_forward">Переслать</string>
<string name="message_select_all">Выбрать все</string> <string name="message_select_all">Выбрать все</string>
<string name="message_unselect_all">Отменить выбор</string> <string name="message_unselect_all">Отменить выбор</string>
<string name="message_restore_from_trash">Восстановить из корзины</string> <string name="message_restore_from_trash">Restore from trash</string>
<string name="message_move_to_trash">Перенести в корзину</string> <string name="message_move_to_trash">Перенести в корзину</string>
<string name="message_delete_forever">Удалить навсегда</string> <string name="message_delete_forever">Удалить навсегда</string>
<string name="message_restore_success">Сообщение успешно восстановлено</string> <string name="message_restore_success">Message restored successfully</string>
<string name="message_delete_success">Сообщение успешно удалено</string> <string name="message_delete_success">Сообщение успешно удалено</string>
<string name="message_mailbox_type_student">ученик</string> <string name="message_mailbox_type_student">ученик</string>
<string name="message_mailbox_type_parent">родитель</string> <string name="message_mailbox_type_parent">родитель</string>
@ -396,10 +396,10 @@
<item quantity="other">%1$d выбрано</item> <item quantity="other">%1$d выбрано</item>
</plurals> </plurals>
<string name="message_messages_deleted">Сообщение удалено</string> <string name="message_messages_deleted">Сообщение удалено</string>
<string name="message_messages_restored">Сообщения восстановлены</string> <string name="message_messages_restored">Messages restored</string>
<string name="message_mailbox_chooser_title">Выбрать почтовый ящик</string> <string name="message_mailbox_chooser_title">Выбрать почтовый ящик</string>
<string name="message_incognito_mode_on">Режим инкогнито включен</string> <string name="message_incognito_mode_on">Incognito mode is on</string>
<string name="message_incognito_description">Благодаря режиму инкогнито отправитель не уведомлен о прочтении сообщения</string> <string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
<!--Note--> <!--Note-->
<string name="note_no_items">Нет записей о замечаниях и свершениях</string> <string name="note_no_items">Нет записей о замечаниях и свершениях</string>
<string name="note_points">Баллы</string> <string name="note_points">Баллы</string>
@ -748,8 +748,8 @@
<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>
<string name="pref_view_timetable_show_groups">Показать группы рядом с темами</string> <string name="pref_view_timetable_show_groups">Показать группы рядом с темами</string>
<string name="pref_view_timetable_show_additional_lessons">Показать дополнительные уроки</string> <string name="pref_view_timetable_show_additional_lessons">Show additional lessons</string>
<string name="pref_view_timetable_show_gaps">Показать пустые поля, где нет уроков</string> <string name="pref_view_timetable_show_gaps">Show empty tiles where there\'s no lesson</string>
<string name="pref_view_grade_statistics_list">Показывать диаграммы в оценках класса</string> <string name="pref_view_grade_statistics_list">Показывать диаграммы в оценках класса</string>
<string name="pref_view_subjects_without_grades">Показать предметы без оценок</string> <string name="pref_view_subjects_without_grades">Показать предметы без оценок</string>
<string name="pref_view_grade_color_scheme">Цветовая схема оценок</string> <string name="pref_view_grade_color_scheme">Цветовая схема оценок</string>
@ -790,12 +790,12 @@
<string name="pref_other_grade_modifier_minus">Стоимость минуса</string> <string name="pref_other_grade_modifier_minus">Стоимость минуса</string>
<string name="pref_other_fill_message_content">Отвечать с историей сообщений</string> <string name="pref_other_fill_message_content">Отвечать с историей сообщений</string>
<string name="pref_other_optional_arithmetic_average">Показывать среднее арифметическое при отсутствии стоимости</string> <string name="pref_other_optional_arithmetic_average">Показывать среднее арифметическое при отсутствии стоимости</string>
<string name="pref_other_incognito_mode">Режим инкогнито</string> <string name="pref_other_incognito_mode">Incognito mode</string>
<string name="pref_other_incognito_mode_summary">Не сообщать о чтении сообщения</string> <string name="pref_other_incognito_mode_summary">Do not inform about reading the message</string>
<string name="pref_ads_support_category_name">Поддержка</string> <string name="pref_ads_support_category_name">Поддержка</string>
<string name="pref_ads_privacy_policy">Политика приватности</string> <string name="pref_ads_privacy_policy">Политика приватности</string>
<string name="pref_ads_agreements">Соглашения</string> <string name="pref_ads_agreements">Соглашения</string>
<string name="pref_ads_consent">Показать согласие на обработку данных</string> <string name="pref_ads_consent">Show consent to data processing</string>
<string name="pref_ads_show_in_app">Показать рекламу в приложении</string> <string name="pref_ads_show_in_app">Показать рекламу в приложении</string>
<string name="pref_ads_support">Посмотреть рекламу для поддержки проекта</string> <string name="pref_ads_support">Посмотреть рекламу для поддержки проекта</string>
<string name="pref_ads_privacy_title">Согласие на обработку данных</string> <string name="pref_ads_privacy_title">Согласие на обработку данных</string>
@ -813,8 +813,8 @@
<string name="pref_dashboard_appearance_header">Главная</string> <string name="pref_dashboard_appearance_header">Главная</string>
<string name="pref_dashboard_appearance_tiles_title">Видимость плиток</string> <string name="pref_dashboard_appearance_tiles_title">Видимость плиток</string>
<string name="pref_attendance_appearance_view">Посещаемость</string> <string name="pref_attendance_appearance_view">Посещаемость</string>
<string name="pref_attendance_calculator_appearance_view">Калькулятор посещаемости</string> <string name="pref_attendance_calculator_appearance_view">Attendance calculator</string>
<string name="pref_attendance_calculator_appearance_settings_title">Настройки</string> <string name="pref_attendance_calculator_appearance_settings_title">Settings</string>
<string name="pref_timetable_appearance_view">Расписание</string> <string name="pref_timetable_appearance_view">Расписание</string>
<string name="pref_grades_advanced_header">Оценки</string> <string name="pref_grades_advanced_header">Оценки</string>
<string name="pref_counted_average_advanced_header">Рассчитанная средняя оценка</string> <string name="pref_counted_average_advanced_header">Рассчитанная средняя оценка</string>
@ -866,33 +866,31 @@
<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">Уважаемый родитель,&lt;br /&gt;&lt;br /&gt;для авторизации и обеспечения безопасности данных, просим Вас ввести ниже номер PESEL &lt;b&gt;%1$s&lt;/b&gt;. Эти данные необходимы для надлежащего доступа к и защиты личных данных в соответствии с действующими нормами.&lt;br /&gt;&lt;br /&gt;После ввода данных мы обеспечим проверку, чтобы доступ к системе VULCAN был предоставлен исключительно уполномоченным лицам. Если у Вас возникли какие-либо сомнения или проблемы, пожалуйста, свяжитесь с администратором школьного дневника для уточнения ситуации.&lt;br /&gt;&lt;br /&gt;Мы соблюдаем наивысшие стандарты защиты персональных данных и гарантируем сохранность всей информации. Приложение Wulkanowy не сохраняет и не обрабатывает номер PESEL.&lt;br /&gt;&lt;br /&gt;Напоминаем, что предоставление точных данных является обязательным и необходимым для использования системы VULCAN.</string> <string name="auth_description">Dear Parent,&lt;br /&gt;&lt;br /&gt;To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student &lt;b&gt;%1$s&lt;/b&gt;. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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</string> <string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string> <string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
<string name="captcha_verified_message">Верификация успешна</string> <string name="captcha_verified_message">Verified successfully</string>
<!--Panic mode-->
<string name="panic_mode_title">Emergency access</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Интернет-соединение отсутствует</string> <string name="error_no_internet">Интернет-соединение отсутствует</string>
<string name="error_invalid_device_datetime">Произошла ошибка. Проверьте время на вашем устройстве</string> <string name="error_invalid_device_datetime">Произошла ошибка. Проверьте время на вашем устройстве</string>
<string name="error_account_inactive">Эта учетная запись неактивна. Попробуйте войти снова</string> <string name="error_account_inactive">This account is inactive. Try logging in again</string>
<string name="error_timeout">Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже</string> <string name="error_timeout">Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже</string>
<string name="error_login_failed">Не удалось загрузить данные, повторите попытку позже</string> <string name="error_login_failed">Не удалось загрузить данные, повторите попытку позже</string>
<string name="error_password_invalid">Ваш пароль устарел или был изменен. Пожалуйста, войдите снова</string> <string name="error_password_invalid">Your password has expired or been changed. Please log in again</string>
<string name="error_password_change_required">Необходимо изменить пароль дневника</string> <string name="error_password_change_required">Необходимо изменить пароль дневника</string>
<string name="error_service_unavailable">UONET+ проводит техническое обслуживание, повторите попытку позже</string> <string name="error_service_unavailable">UONET+ проводит техническое обслуживание, повторите попытку позже</string>
<string name="error_unknown_uonet">Неизвестная ошибка дневника UONET+, повторите попытку позже</string> <string name="error_unknown_uonet">Неизвестная ошибка дневника UONET+, повторите попытку позже</string>
<string name="error_unknown_app">Неизвестная ошибка приложения, повторите попытку позже</string> <string name="error_unknown_app">Неизвестная ошибка приложения, повторите попытку позже</string>
<string name="error_cloudflare_captcha">Требуется подтверждение капчи</string> <string name="error_cloudflare_captcha">Captcha verification required</string>
<string name="error_unknown">Произошла непредвиденная ошибка</string> <string name="error_unknown">Произошла непредвиденная ошибка</string>
<string name="error_feature_disabled">Функция отключена вашей школой</string> <string name="error_feature_disabled">Функция отключена вашей школой</string>
<string name="error_feature_not_available">Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом</string> <string name="error_feature_not_available">Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом</string>
<string name="error_field_required">Это поле обязательно</string> <string name="error_field_required">Это поле обязательно</string>
<!-- Mute system --> <!-- Mute system -->
<string name="message_mute">Отключить уведомления</string> <string name="message_mute">Mute</string>
<string name="message_unmute">Включить уведомления</string> <string name="message_unmute">Unmute</string>
<string name="message_mute_success">Вы отключили уведомления от этого пользователя</string> <string name="message_mute_success">You have muted this user</string>
<string name="message_unmute_success">Вы включили уведомления от этого пользователя снова</string> <string name="message_unmute_success">You have unmuted this user</string>
</resources> </resources>

View File

@ -29,7 +29,7 @@
<string name="notifications_center_title">Centrum oznámení</string> <string name="notifications_center_title">Centrum oznámení</string>
<string name="menu_order_title">Konfigurácia menu</string> <string name="menu_order_title">Konfigurácia menu</string>
<!--Subtitles--> <!--Subtitles-->
<string name="grade_subtitle">Polrok %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 žiackeho alebo rodičovského účtu</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: &lt;b&gt;%1$s&lt;/b&gt;</string> <string name="login_header_symbol">Zadajte symbol zo stránky denníka: &lt;b&gt;%1$s&lt;/b&gt;</string>
@ -105,8 +105,8 @@
<string name="main_support_positive">Zapnúť reklamy</string> <string name="main_support_positive">Zapnúť reklamy</string>
<!--Grade--> <!--Grade-->
<string name="grade_header">Známka</string> <string name="grade_header">Známka</string>
<string name="grade_semester">Polrok %d</string> <string name="grade_semester">Semester %d</string>
<string name="grade_switch_semester">Zmeniť polrok</string> <string name="grade_switch_semester">Zmeniť semester</string>
<string name="grade_no_items">Žiadne známky</string> <string name="grade_no_items">Žiadne známky</string>
<string name="grade_weight">Váha</string> <string name="grade_weight">Váha</string>
<string name="grade_weight_value">Váha: %s</string> <string name="grade_weight_value">Váha: %s</string>
@ -125,16 +125,16 @@
<string name="grade_summary_calculated_average">Vypočítaný polročný priemer</string> <string name="grade_summary_calculated_average">Vypočítaný polročný priemer</string>
<string name="grade_summary_calculated_average_annual">Vypočítaný ročný priemer</string> <string name="grade_summary_calculated_average_annual">Vypočítaný ročný priemer</string>
<string name="grade_summary_calculated_average_help_dialog_title">Ako funguje vypočítaný priemer?</string> <string name="grade_summary_calculated_average_help_dialog_title">Ako funguje vypočítaný priemer?</string>
<string name="grade_summary_calculated_average_help_dialog_message">Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastavení aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Pokiaľ 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 je možné zmeniť vynútením výpočtu priemeru v nastavení aplikácie.\n\n<b>Priemer známok iba z vybraného polroka</b>:\n1. Výpočet váženého priemeru pre každý predmet v danom polroku\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru sčítaných priemerov\n\n<b>Priemer priemerov z oboch polrokov</b>:\n1. Výpočet váženého priemeru pre každý predmet v polroku 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za polrok 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru sčítaný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. polroku je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru sčítaný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>
<string name="grade_summary_final_average_help_dialog_message">Konečný priemer je aritmetický priemer vypočítaný zo všetkých aktuálne dostupných konečných známok v danom polroku.\n\nSchéma výpočtu sa skladá z nasledujúcich krokov:\n1. Sčítanie konečných známok zadaných učiteľmi\n2. Delené počtom predmetov, pre ktoré už boli udelené známky</string> <string name="grade_summary_final_average_help_dialog_message">Konečný priemer je aritmetický priemer vypočítaný zo všetkých aktuálne dostupných konečných známok v danom semestri.\n\nSchéma výpočtu sa skladá z nasledujúcich krokov:\n1. Sčítanie konečných známok zadaných učiteľmi\n2. Delené počtom predmetov, pre ktoré už boli vydané známky</string>
<string name="grade_summary_final_average">Konečný priemer</string> <string name="grade_summary_final_average">Konečný priemer</string>
<string name="grade_summary_from_subjects">z %1$d z %2$d predmetov</string> <string name="grade_summary_from_subjects">z %1$d z %2$d predmetov</string>
<string name="grade_menu_summary">Zhrnutie</string> <string name="grade_menu_summary">Zhrnutie</string>
<string name="grade_menu_statistics">Trieda</string> <string name="grade_menu_statistics">Trieda</string>
<string name="grade_menu_read">Označiť ako prečítané</string> <string name="grade_menu_read">Označiť ako prečítané</string>
<string name="grade_statistics_partial">Čiastočné</string> <string name="grade_statistics_partial">Čiastočné</string>
<string name="grade_statistics_semester">Polrok</string> <string name="grade_statistics_semester">Semester</string>
<string name="grade_statistics_points">Body</string> <string name="grade_statistics_points">Body</string>
<string name="grade_statistics_legend">Vysvetlivky</string> <string name="grade_statistics_legend">Vysvetlivky</string>
<string name="grade_statistics_class_average">Priemer triedy: %1$s</string> <string name="grade_statistics_class_average">Priemer triedy: %1$s</string>
@ -499,7 +499,7 @@
<string name="mobile_devices_title">Mobilný prístup</string> <string name="mobile_devices_title">Mobilný prístup</string>
<string name="mobile_devices_no_items">Žiadne zariadenia</string> <string name="mobile_devices_no_items">Žiadne zariadenia</string>
<string name="mobile_devices_unregister">Zrušiť registráciu</string> <string name="mobile_devices_unregister">Zrušiť registráciu</string>
<string name="mobile_device_removed">Zariadenie odstránené</string> <string name="mobile_device_removed">Zariadenie odstránenie</string>
<string name="mobile_device_qr">QR kód</string> <string name="mobile_device_qr">QR kód</string>
<string name="mobile_device_token">Token</string> <string name="mobile_device_token">Token</string>
<string name="mobile_device_symbol">Symbol</string> <string name="mobile_device_symbol">Symbol</string>
@ -872,8 +872,6 @@
<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>
<string name="captcha_dialog_description"><b>Prečo sa mi to zobrazuje?</b>\nWebová stránka denníka, z ktorej Wulkanowy sťahuje dáta, zobrazuje rovnakú obrazovku ako vyššie, takže Wulkanowy ju musí tiež zobraziť, aby bolo možné získavať dáta z tejto stránky. Nedá sa to obísť</string> <string name="captcha_dialog_description"><b>Prečo sa mi to zobrazuje?</b>\nWebová stránka denníka, z ktorej Wulkanowy sťahuje dáta, zobrazuje rovnakú obrazovku ako vyššie, takže Wulkanowy ju musí tiež zobraziť, aby bolo možné získavať dáta z tejto stránky. Nedá sa to obísť</string>
<string name="captcha_verified_message">Úspešne overené</string> <string name="captcha_verified_message">Úspešne overené</string>
<!--Panic mode-->
<string name="panic_mode_title">Núdzový prístup</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Žiadne internetové pripojenie</string> <string name="error_no_internet">Žiadne internetové pripojenie</string>
<string name="error_invalid_device_datetime">Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia</string> <string name="error_invalid_device_datetime">Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia</string>

View File

@ -872,8 +872,6 @@
<string name="captcha_dialog_title">Веб-сайт VULCAN потребує підтвердження</string> <string name="captcha_dialog_title">Веб-сайт VULCAN потребує підтвердження</string>
<string name="captcha_dialog_description"><b>Чому я це бачу?</b>\nСайт реєстру, з якого Wulkanowy завантажує дані, відображає той самий екран, що й вище, тому Wulkanowy також повинен показувати його, щоб мати змогу завантажувати дані з цього сайту. Це неможливо обійти</string> <string name="captcha_dialog_description"><b>Чому я це бачу?</b>\nСайт реєстру, з якого Wulkanowy завантажує дані, відображає той самий екран, що й вище, тому Wulkanowy також повинен показувати його, щоб мати змогу завантажувати дані з цього сайту. Це неможливо обійти</string>
<string name="captcha_verified_message">Верифікація завершена</string> <string name="captcha_verified_message">Верифікація завершена</string>
<!--Panic mode-->
<string name="panic_mode_title">Екстрений доступ</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Немає з\'єднання з інтернетом</string> <string name="error_no_internet">Немає з\'єднання з інтернетом</string>
<string name="error_invalid_device_datetime">Сталася помилка. Перевірте годинник пристрою</string> <string name="error_invalid_device_datetime">Сталася помилка. Перевірте годинник пристрою</string>

View File

@ -28,7 +28,7 @@
<string name="student_info_title">Student info</string> <string name="student_info_title">Student info</string>
<string name="dashboard_title">Dashboard</string> <string name="dashboard_title">Dashboard</string>
<string name="notifications_center_title">Notifications center</string> <string name="notifications_center_title">Notifications center</string>
<string name="menu_order_title">Menu configuration</string> <string name="menu_order_title">Menu configuartion</string>
<!--Subtitles--> <!--Subtitles-->
@ -869,9 +869,6 @@
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string> <string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
<string name="captcha_verified_message">Verified successfully</string> <string name="captcha_verified_message">Verified successfully</string>
<!--Panic mode-->
<string name="panic_mode_title">Emergency access</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">No internet connection</string> <string name="error_no_internet">No internet connection</string>

View File

@ -8,7 +8,6 @@ import io.mockk.mockk
fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>() fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>()
.apply { .apply {
every { createBase() } returns sdk every { create() } returns sdk
coEvery { create() } returns sdk
coEvery { create(any(), any()) } returns sdk coEvery { create(any(), any()) } returns sdk
} }

View File

@ -11,6 +11,7 @@ import io.github.wulkanowy.sdk.pojo.RegisterStudent
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerify import io.mockk.coVerify
import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.spyk import io.mockk.spyk
@ -39,13 +40,11 @@ class WulkanowySdkFactoryTest {
chuckerInterceptor = mockk(), chuckerInterceptor = mockk(),
remoteConfig = mockk(relaxed = true), remoteConfig = mockk(relaxed = true),
webkitCookieManagerProxy = mockk(), webkitCookieManagerProxy = mockk(),
studentDb = studentDao, studentDb = studentDao
wulkanowyRepository = mockk(relaxed = true),
context = mockk(),
) )
) )
coEvery { wulkanowySdkFactory.create() } returns sdk every { wulkanowySdkFactory.create() } returns sdk
} }
@Test @Test

View File

@ -0,0 +1,161 @@
package io.github.wulkanowy.data.db.dao
import android.content.Context
import android.os.Build
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import dagger.hilt.android.testing.HiltTestApplication
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.getStudentEntity
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class StudentDaoTest {
private lateinit var studentDao: StudentDao
private lateinit var semesterDao: SemesterDao
private lateinit var db: AppDatabase
@Before
fun createDb() {
val context: Context = ApplicationProvider.getApplicationContext()
db = Room.inMemoryDatabaseBuilder(
context = context,
klass = AppDatabase::class.java
).build()
studentDao = db.studentDao
semesterDao = db.semesterDao
}
@Test
fun `get students associated with correct semester with same studentId`() = runTest {
val notEduOneStudent = getStudentEntity()
.copy(
isEduOne = false,
classId = 42,
studentId = 100
)
.apply { id = 1 }
val eduOneStudent = getStudentEntity()
.copy(
isEduOne = true,
classId = 0,
studentId = 100
)
.apply { id = 2 }
val semesterAssociatedWithNotEduOneStudent = getSemesterEntity()
.copy(
studentId = notEduOneStudent.studentId,
classId = notEduOneStudent.classId,
diaryId = 1 // make semester unique
)
.apply { id = 0 }
val semesterAssociatedWithEduOneStudent = getSemesterEntity()
.copy(
studentId = eduOneStudent.studentId,
classId = eduOneStudent.classId,
diaryId = 2 // make semester unique
)
.apply { id = 0 }
studentDao.insertAll(listOf(notEduOneStudent, eduOneStudent))
semesterDao.insertAll(
listOf(
semesterAssociatedWithNotEduOneStudent,
semesterAssociatedWithEduOneStudent
)
)
val studentsWithSemesters = studentDao.loadStudentsWithSemesters()
val notEduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == notEduOneStudent.id }
?.value
val eduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == eduOneStudent.id }
?.value
assertEquals(2, studentsWithSemesters.size)
assertEquals(1, notEduOneSemestersResult?.size)
assertEquals(1, eduOneSemestersResult?.size)
assertEquals(semesterAssociatedWithEduOneStudent, eduOneSemestersResult?.firstOrNull())
assertEquals(
semesterAssociatedWithNotEduOneStudent,
notEduOneSemestersResult?.firstOrNull()
)
}
@Test
fun `get students associated with correct semester with different studentId`() = runTest {
val notEduOneStudent = getStudentEntity()
.copy(
isEduOne = false,
classId = 42,
studentId = 100
)
.apply { id = 1 }
val eduOneStudent = getStudentEntity()
.copy(
isEduOne = true,
classId = 0,
studentId = 101
)
.apply { id = 2 }
val semesterAssociatedWithNotEduOneStudent = getSemesterEntity()
.copy(
studentId = notEduOneStudent.studentId,
classId = notEduOneStudent.classId,
)
.apply { id = 0 }
val semesterAssociatedWithEduOneStudent = getSemesterEntity()
.copy(
studentId = eduOneStudent.studentId,
classId = eduOneStudent.classId,
)
.apply { id = 0 }
studentDao.insertAll(listOf(notEduOneStudent, eduOneStudent))
semesterDao.insertAll(
listOf(
semesterAssociatedWithNotEduOneStudent,
semesterAssociatedWithEduOneStudent
)
)
val studentsWithSemesters = studentDao.loadStudentsWithSemesters()
val notEduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == notEduOneStudent.id }
?.value
val eduOneSemestersResult = studentsWithSemesters.entries
.find { (student, _) -> student.id == eduOneStudent.id }
?.value
assertEquals(2, studentsWithSemesters.size)
assertEquals(1, notEduOneSemestersResult?.size)
assertEquals(1, eduOneSemestersResult?.size)
assertEquals(semesterAssociatedWithEduOneStudent, eduOneSemestersResult?.firstOrNull())
assertEquals(
semesterAssociatedWithNotEduOneStudent,
notEduOneSemestersResult?.firstOrNull()
)
}
@After
fun closeDb() {
db.close()
}
}

View File

@ -12,9 +12,7 @@ import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import kotlin.random.Random import kotlin.random.Random
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertTrue
@HiltAndroidTest @HiltAndroidTest
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@ -22,9 +20,10 @@ import kotlin.test.assertTrue
class Migration63Test : AbstractMigrationTest() { class Migration63Test : AbstractMigrationTest() {
@Test @Test
fun `update is_edu_one to null if 0`() = runTest { fun `update is_edu_one to null`() = runTest {
with(helper.createDatabase(dbName, 62)) { with(helper.createDatabase(dbName, 62)) {
createStudent(1, 0) createStudent(1, 0)
createStudent(2, 1)
close() close()
} }
@ -32,31 +31,15 @@ class Migration63Test : AbstractMigrationTest() {
val database = getMigratedRoomDatabase() val database = getMigratedRoomDatabase()
val studentDb = database.studentDao val studentDb = database.studentDao
val student = studentDb.loadById(1) val student1 = studentDb.loadById(1)
val student2 = studentDb.loadById(2)
assertNull(student!!.isEduOne) assertNull(student1!!.isEduOne)
assertNull(student2!!.isEduOne)
database.close() 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) { private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) {
insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("scrapper_base_url", "https://fakelog.cf") put("scrapper_base_url", "https://fakelog.cf")

View File

@ -50,7 +50,7 @@ class SemesterRepositoryTest {
getSemesterPojo(1, 2, now().minusMonths(3), now()) getSemesterPojo(1, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student) } returns emptyList()
coEvery { sdk.getSemesters() } returns semesters coEvery { sdk.getSemesters() } returns semesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -77,10 +77,7 @@ class SemesterRepositoryTest {
) )
coEvery { coEvery {
semesterDb.loadAll( semesterDb.loadAll(student)
student.studentId,
student.classId
)
} returns badSemesters.mapToEntities(student.studentId) } returns badSemesters.mapToEntities(student.studentId)
coEvery { sdk.getSemesters() } returns goodSemesters coEvery { sdk.getSemesters() } returns goodSemesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -103,7 +100,7 @@ class SemesterRepositoryTest {
getSemesterPojo(2, 3, now(), now().plusMonths(6)), getSemesterPojo(2, 3, now(), now().plusMonths(6)),
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returnsMany listOf( coEvery { semesterDb.loadAll(any()) } returnsMany listOf(
badSemesters.mapToEntities(student.studentId), badSemesters.mapToEntities(student.studentId),
badSemesters.mapToEntities(student.studentId), badSemesters.mapToEntities(student.studentId),
goodSemesters.mapToEntities(student.studentId) goodSemesters.mapToEntities(student.studentId)
@ -125,7 +122,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1))
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -138,7 +135,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now().minusMonths(3), now()) getSemesterEntity(1, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -151,7 +148,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now(), now()) getSemesterEntity(1, 2, now(), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = runBlocking { semesterRepository.getSemesters(student) } val items = runBlocking { semesterRepository.getSemesters(student) }
assertEquals(2, items.size) assertEquals(2, items.size)
@ -164,7 +161,7 @@ class SemesterRepositoryTest {
getSemesterPojo(1, 2, now().minusMonths(3), now()) getSemesterPojo(1, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student) } returns emptyList()
coEvery { sdk.getSemesters() } returns semesters coEvery { sdk.getSemesters() } returns semesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -194,10 +191,7 @@ class SemesterRepositoryTest {
) )
coEvery { coEvery {
semesterDb.loadAll( semesterDb.loadAll(student)
student.studentId,
student.classId
)
} returns semestersWithNoCurrent } returns semestersWithNoCurrent
coEvery { sdk.getSemesters() } returns newSemesters coEvery { sdk.getSemesters() } returns newSemesters
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
@ -214,7 +208,7 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1)) getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1))
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
assertEquals(2, items.size) assertEquals(2, items.size)
@ -227,14 +221,14 @@ class SemesterRepositoryTest {
getSemesterEntity(1, 1, now(), now()) getSemesterEntity(1, 1, now(), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters coEvery { semesterDb.loadAll(student) } returns semesters
runBlocking { semesterRepository.getCurrentSemester(student) } runBlocking { semesterRepository.getCurrentSemester(student) }
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun getCurrentSemester_emptyList() { fun getCurrentSemester_emptyList() {
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student) } returns emptyList()
coEvery { sdk.getSemesters() } returns emptyList() coEvery { sdk.getSemesters() } returns emptyList()
runBlocking { semesterRepository.getCurrentSemester(student) } runBlocking { semesterRepository.getCurrentSemester(student) }

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.main
import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.MainCoroutineRule
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.WulkanowyRepository
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AdsHelper
@ -32,9 +31,6 @@ class MainPresenterTest {
@MockK @MockK
lateinit var studentRepository: StudentRepository lateinit var studentRepository: StudentRepository
@MockK(relaxed = true)
lateinit var wulkanowyRepository: WulkanowyRepository
@MockK(relaxed = true) @MockK(relaxed = true)
lateinit var prefRepository: PreferencesRepository lateinit var prefRepository: PreferencesRepository
@ -69,8 +65,7 @@ class MainPresenterTest {
analytics = analytics, analytics = analytics,
json = Json, json = Json,
appInfo = appInfo, appInfo = appInfo,
adsHelper = adsHelper, adsHelper = adsHelper
wulkanowyRepository = wulkanowyRepository
) )
presenter.onAttachView(mainView, null) presenter.onAttachView(mainView, null)
} }

View File

@ -1,6 +1,6 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.9.24' kotlin_version = '1.9.23'
about_libraries = '11.1.4' about_libraries = '11.1.4'
hilt_version = '2.51.1' hilt_version = '2.51.1'
} }
@ -13,12 +13,12 @@ buildscript {
dependencies { dependencies {
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.20" classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.19"
classpath 'com.android.tools.build:gradle:8.4.1' classpath 'com.android.tools.build:gradle:8.4.0'
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:3.0.1' 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:5.0.0.4638" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.0.0.4638"