Compare commits
6 Commits
2.6.11
...
bugfix/cla
Author | SHA1 | Date | |
---|---|---|---|
2bae532f6d | |||
157e04b239 | |||
2a0ac7f91e | |||
d7b1a08098 | |||
985be92a4d | |||
6616a313e2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -127,4 +127,3 @@ google-services.json
|
||||
!app/google-services.json
|
||||
|
||||
|
||||
.idea/appInsightsSettings.xml
|
||||
|
@ -27,8 +27,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 171
|
||||
versionName "2.6.11"
|
||||
versionCode 161
|
||||
versionName "2.6.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
@ -160,8 +160,8 @@ play {
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.99d
|
||||
updatePriority = 2
|
||||
userFraction = 0.25d
|
||||
updatePriority = 1
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
@ -187,17 +187,16 @@ ext {
|
||||
room = "2.6.1"
|
||||
chucker = "4.0.0"
|
||||
mockk = "1.13.10"
|
||||
coroutines = "1.8.1"
|
||||
coroutines = "1.8.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.6.10'
|
||||
implementation 'io.github.wulkanowy:sdk:2.6.0'
|
||||
|
||||
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-coroutines-android:$coroutines"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines"
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
@ -205,7 +204,6 @@ dependencies {
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.7.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.recyclerview:recyclerview:1.3.2"
|
||||
|
@ -3,8 +3,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="androidx.javascriptengine" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
@ -44,9 +42,9 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/WulkanowyTheme"
|
||||
android:resizeableActivity="true"
|
||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
|
@ -13,8 +13,8 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.github.wulkanowy.data.api.services.SchoolsService
|
||||
import io.github.wulkanowy.data.api.services.WulkanowyService
|
||||
import io.github.wulkanowy.data.api.AdminMessageService
|
||||
import io.github.wulkanowy.data.api.SchoolsService
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
@ -71,7 +71,7 @@ internal class DataModule {
|
||||
okHttpClient: OkHttpClient,
|
||||
json: Json,
|
||||
appInfo: AppInfo
|
||||
): WulkanowyService = Retrofit.Builder()
|
||||
): AdminMessageService = Retrofit.Builder()
|
||||
.baseUrl(appInfo.messagesBaseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
|
@ -1,21 +1,13 @@
|
||||
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.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.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
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.scrapper.EvaluateHandler
|
||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
@ -24,24 +16,18 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class WulkanowySdkFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val chuckerInterceptor: ChuckerInterceptor,
|
||||
private val remoteConfig: RemoteConfigHelper,
|
||||
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
||||
private val studentDb: StudentDao,
|
||||
private val wulkanowyRepository: WulkanowyRepository,
|
||||
) {
|
||||
|
||||
private val eduOneMutex = Mutex()
|
||||
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 {
|
||||
androidVersion = Build.VERSION.RELEASE
|
||||
buildTag = Build.MODEL
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
setAdditionalCookieManager(webkitCookieManagerProxy)
|
||||
@ -50,46 +36,14 @@ class WulkanowySdkFactory @Inject constructor(
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
|
||||
fun createBase() = sdk
|
||||
|
||||
suspend fun create(): Sdk {
|
||||
val mapping = wulkanowyRepository.getMapping()
|
||||
|
||||
return createBase().apply {
|
||||
if (mapping != null) {
|
||||
endpointsMapping = mapping.endpoints
|
||||
vTokenMapping = mapping.vTokens
|
||||
vHeaders = mapping.vHeaders
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun create() = sdk
|
||||
|
||||
suspend fun create(student: Student, semester: Semester? = null): Sdk {
|
||||
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
|
||||
return buildSdk(student, semester, overrideIsEduOne)
|
||||
}
|
||||
|
||||
private suspend fun buildSdk(
|
||||
student: Student,
|
||||
semester: Semester?,
|
||||
isStudentEduOne: Boolean
|
||||
): Sdk {
|
||||
private fun buildSdk(student: Student, semester: Semester?, isStudentEduOne: Boolean): Sdk {
|
||||
return create().apply {
|
||||
email = student.email
|
||||
password = student.password
|
||||
|
@ -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 retrofit2.http.GET
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
interface WulkanowyService {
|
||||
interface AdminMessageService {
|
||||
|
||||
@GET("/v1.json")
|
||||
suspend fun getAdminMessages(): List<AdminMessage>
|
||||
|
||||
@GET("/mapping2.json")
|
||||
suspend fun getMapping(): Mapping
|
||||
}
|
||||
}
|
@ -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.LoginEvent
|
@ -1,20 +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(),
|
||||
)
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -11,5 +12,16 @@ import javax.inject.Singleton
|
||||
interface SchoolDao : BaseDao<School> {
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ -14,6 +15,17 @@ interface SemesterDao : BaseDao<Semester> {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
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)")
|
||||
suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
|
||||
@Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId)")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,13 +47,9 @@ abstract class StudentDao {
|
||||
abstract suspend fun loadAll(): List<Student>
|
||||
|
||||
@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>>
|
||||
|
||||
@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")
|
||||
abstract suspend fun updateCurrent(id: Long)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Teacher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
@ -11,5 +12,16 @@ import javax.inject.Singleton
|
||||
interface TeacherDao : BaseDao<Teacher> {
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
class Migration63 : AutoMigrationSpec {
|
||||
|
||||
override fun onPostMigrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
package io.github.wulkanowy.data.exceptions
|
||||
|
||||
class NoSuchStudentException(id: Long) :
|
||||
Exception("There is no student with id $id in database")
|
@ -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 }
|
||||
}
|
@ -9,7 +9,6 @@ import com.fredporciuncula.flow.preferences.Preference
|
||||
import com.fredporciuncula.flow.preferences.Serializer
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
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.AttendanceCalculatorSortingMode
|
||||
import io.github.wulkanowy.data.enums.GradeColorTheme
|
||||
@ -376,15 +375,6 @@ class PreferencesRepository @Inject constructor(
|
||||
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
|
||||
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 {
|
||||
if (installationId.isEmpty()) {
|
||||
installationId = UUID.randomUUID().toString()
|
||||
|
@ -36,7 +36,7 @@ class SchoolRepository @Inject constructor(
|
||||
)
|
||||
it == null || forceRefresh || isExpired
|
||||
},
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
query = { schoolDb.load(student) },
|
||||
fetch = {
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getSchool()
|
||||
|
@ -1,7 +1,7 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
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.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
|
@ -27,11 +27,11 @@ class SemesterRepository @Inject constructor(
|
||||
forceRefresh: Boolean = false,
|
||||
refreshOnNoCurrent: Boolean = false
|
||||
) = withContext(dispatchers.io) {
|
||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
val semesters = semesterDb.loadAll(student)
|
||||
|
||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||
refreshSemesters(student)
|
||||
semesterDb.loadAll(student.studentId, student.classId)
|
||||
semesterDb.loadAll(student)
|
||||
} else semesters
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ class SemesterRepository @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val old = semesterDb.loadAll(student.studentId, student.classId)
|
||||
val old = semesterDb.loadAll(student)
|
||||
semesterDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
|
@ -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.StudentWithSemesters
|
||||
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.mapToPojo
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
@ -65,7 +66,8 @@ class StudentRepository @Inject constructor(
|
||||
.mapToPojo(password)
|
||||
.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) ->
|
||||
StudentWithSemesters(
|
||||
student = student.apply {
|
||||
@ -80,22 +82,25 @@ class StudentRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true): StudentWithSemesters? =
|
||||
studentDb.loadStudentWithSemestersById(id).let { res ->
|
||||
StudentWithSemesters(
|
||||
student = res.keys.firstOrNull() ?: return null,
|
||||
semesters = res.values.first(),
|
||||
)
|
||||
}.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true): List<Student> {
|
||||
val students = studentDb.loadAll()
|
||||
if (!decryptPass) return students
|
||||
|
||||
return students.map { student ->
|
||||
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
|
||||
return@map student
|
||||
}
|
||||
|
||||
student.apply {
|
||||
password = withContext(dispatchers.io) {
|
||||
scrambler.decrypt(student.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
@ -123,7 +128,7 @@ class StudentRepository @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
val currentStudentSemesters = semesterDb.loadAll(student)
|
||||
if (currentStudentSemesters.isEmpty()) {
|
||||
Timber.d("Check isAuthorized: apply empty semesters workaround")
|
||||
semesterDb.insertSemesters(
|
||||
@ -181,8 +186,8 @@ class StudentRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||
studentDb.switchCurrent(studentWithSemesters.student.id)
|
||||
suspend fun switchStudent(student: Student) {
|
||||
studentDb.switchCurrent(student.id)
|
||||
}
|
||||
|
||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||
@ -190,8 +195,8 @@ class StudentRepository @Inject constructor(
|
||||
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
|
||||
studentDb.update(studentNickAndAvatar)
|
||||
|
||||
suspend fun isOneUniqueStudent() = getSavedStudents(false)
|
||||
.distinctBy { it.student.studentName }.size == 1
|
||||
suspend fun isOneUniqueStudent() = studentDb.loadAll()
|
||||
.distinctBy { it.studentName }.size == 1
|
||||
|
||||
suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) =
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
@ -199,7 +204,7 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
suspend fun refreshStudentAfterAuthorize(student: Student, semester: 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}") }
|
||||
.getOrNull() ?: return
|
||||
|
||||
@ -209,7 +214,7 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
studentDb.update(studentName)
|
||||
semesterDb.removeOldAndSaveNew(
|
||||
oldItems = semesterDb.loadAll(student.studentId, semester.classId),
|
||||
oldItems = semesterDb.loadAll(student),
|
||||
newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId)
|
||||
)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class TeacherRepository @Inject constructor(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
query = { teacherDb.loadAll(student) },
|
||||
fetch = {
|
||||
wulkanowySdkFactory.create(student, semester)
|
||||
.getTeachers()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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.enums.MessageType
|
||||
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.WulkanowyRepository
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||
private val wulkanowyRepository: WulkanowyRepository,
|
||||
private val adminMessageRepository: AdminMessageRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
@ -22,7 +22,7 @@ class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||
}
|
||||
|
||||
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
|
||||
return wulkanowyRepository.getAdminMessages().mapResourceData { adminMessages ->
|
||||
return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
|
||||
adminMessages
|
||||
.asSequence()
|
||||
.filter { it.isNotDismissed() }
|
||||
|
@ -59,7 +59,7 @@ class GetMailboxByStudentUseCase @Inject constructor(
|
||||
private fun String.getUnauthorizedVersion(): String {
|
||||
return normalizeStudentName().split(" ")
|
||||
.joinToString(" ") {
|
||||
it.firstOrNull()?.toString().orEmpty() + "*".repeat((it.length - 1).coerceAtLeast(0))
|
||||
it.first() + "*".repeat(it.length - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class AccountPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }
|
||||
resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
|
||||
.logResourceStatus("load account data")
|
||||
.onResourceSuccess { view?.updateData(createAccountItems(it)) }
|
||||
.onResourceError(errorHandler::dispatch)
|
||||
|
@ -1,9 +1,15 @@
|
||||
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.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.resourceFlow
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
@ -14,6 +20,7 @@ import javax.inject.Inject
|
||||
class AccountDetailsPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val semeRepository: SemesterRepository,
|
||||
private val syncManager: SyncManager
|
||||
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
|
||||
|
||||
@ -46,7 +53,12 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
.onResourceLoading {
|
||||
view?.run {
|
||||
@ -85,7 +97,7 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
|
||||
Timber.i("Select student ${studentWithSemesters!!.student.id}")
|
||||
|
||||
resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) }
|
||||
resourceFlow { studentRepository.switchStudent(studentWithSemesters!!.student) }
|
||||
.logResourceStatus("change student")
|
||||
.onResourceSuccess { view?.recreateMainView() }
|
||||
.onResourceNotLoading { view?.popViewToMain() }
|
||||
@ -122,10 +134,12 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
syncManager.stopSyncWorker()
|
||||
openClearLoginView()
|
||||
}
|
||||
|
||||
studentWithSemesters?.student?.isCurrent == true -> {
|
||||
Timber.i("Logout result: Logout student and switch to another")
|
||||
recreateMainView()
|
||||
}
|
||||
|
||||
else -> {
|
||||
Timber.i("Logout result: Logout student")
|
||||
recreateMainView()
|
||||
|
@ -1,8 +1,12 @@
|
||||
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.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.resourceFlow
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.account.AccountItem
|
||||
@ -40,7 +44,7 @@ class AccountQuickPresenter @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
resourceFlow { studentRepository.switchStudent(studentWithSemesters) }
|
||||
resourceFlow { studentRepository.switchStudent(studentWithSemesters.student) }
|
||||
.logResourceStatus("change student")
|
||||
.onResourceSuccess { view?.recreateMainView() }
|
||||
.onResourceNotLoading { view?.popView() }
|
||||
|
@ -59,7 +59,7 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||
webView = this
|
||||
with(settings) {
|
||||
javaScriptEnabled = true
|
||||
userAgentString = wulkanowySdkFactory.createBase().userAgent
|
||||
userAgentString = wulkanowySdkFactory.create().userAgent
|
||||
}
|
||||
|
||||
webViewClient = object : WebViewClient() {
|
||||
|
@ -118,6 +118,5 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
inAppUpdateHelper.onResume()
|
||||
presenter.updateSdkMappings()
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.login
|
||||
|
||||
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.ErrorHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginPresenter @Inject constructor(
|
||||
private val wulkanowyRepository: WulkanowyRepository,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<LoginView>(errorHandler, studentRepository) {
|
||||
@ -19,11 +16,4 @@ class LoginPresenter @Inject constructor(
|
||||
view.initView()
|
||||
Timber.i("Login view was initialized")
|
||||
}
|
||||
|
||||
fun updateSdkMappings() {
|
||||
presenterScope.launch {
|
||||
runCatching { wulkanowyRepository.fetchMapping() }
|
||||
.onFailure { Timber.e(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
private fun loadData() {
|
||||
resetSelectedState()
|
||||
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
|
||||
resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }.onEach {
|
||||
students = it.dataOrNull.orEmpty()
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Login student select students load started")
|
||||
|
@ -35,7 +35,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
|
||||
resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }.onEach {
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Lucky number widget configure students data load")
|
||||
is Resource.Success -> {
|
||||
|
@ -132,7 +132,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
||||
private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking {
|
||||
try {
|
||||
val students = studentRepository.getSavedStudents()
|
||||
val student = students.singleOrNull { it.student.id == studentId }?.student
|
||||
val student = students.singleOrNull { it.id == studentId }
|
||||
val currentStudent = when {
|
||||
student != null -> student
|
||||
studentId != 0L && studentRepository.isCurrentStudentSet() -> {
|
||||
|
@ -138,7 +138,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
inAppUpdateHelper.onResume()
|
||||
presenter.updateSdkMappings()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
@ -6,7 +6,6 @@ import io.github.wulkanowy.data.onResourceError
|
||||
import io.github.wulkanowy.data.onResourceSuccess
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.WulkanowyRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
@ -30,7 +29,6 @@ class MainPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val wulkanowyRepository: WulkanowyRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val analytics: AnalyticsHelper,
|
||||
private val json: Json,
|
||||
@ -87,7 +85,7 @@ class MainPresenter @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }
|
||||
resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
|
||||
.logResourceStatus("load student avatar")
|
||||
.onResourceSuccess {
|
||||
studentsWitSemesters = it
|
||||
@ -201,11 +199,4 @@ class MainPresenter @Inject constructor(
|
||||
.onFailure { errorHandler.dispatch(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSdkMappings() {
|
||||
presenterScope.launch {
|
||||
runCatching { wulkanowyRepository.fetchMapping() }
|
||||
.onFailure { Timber.e(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,22 +42,24 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Timetable widget configure students data load")
|
||||
is Resource.Success -> {
|
||||
val selectedStudentId = appWidgetId?.let { id ->
|
||||
sharedPref.getLong(getStudentWidgetKey(id), 0)
|
||||
} ?: -1
|
||||
when {
|
||||
it.data.isEmpty() -> view?.openLoginView()
|
||||
it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student)
|
||||
else -> view?.updateData(it.data, selectedStudentId)
|
||||
resourceFlow { studentRepository.getSavedStudentsWithSemesters(false) }
|
||||
.onEach {
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Timetable widget configure students data load")
|
||||
is Resource.Success -> {
|
||||
val selectedStudentId = appWidgetId?.let { id ->
|
||||
sharedPref.getLong(getStudentWidgetKey(id), 0)
|
||||
} ?: -1
|
||||
when {
|
||||
it.data.isEmpty() -> view?.openLoginView()
|
||||
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?) {
|
||||
|
@ -95,7 +95,7 @@ class TimetableWidgetFactory(
|
||||
|
||||
private suspend fun getStudent(studentId: Long): Student? {
|
||||
val students = studentRepository.getSavedStudents()
|
||||
return students.singleOrNull { it.student.id == studentId }?.student
|
||||
return students.singleOrNull { it.id == studentId }
|
||||
}
|
||||
|
||||
private suspend fun getLessons(
|
||||
|
@ -2,7 +2,11 @@ package io.github.wulkanowy.ui.modules.timetablewidget
|
||||
|
||||
import android.app.PendingIntent
|
||||
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.Context
|
||||
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.ui.modules.Destination
|
||||
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.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@ -244,7 +255,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
||||
|
||||
private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try {
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
val student = students.singleOrNull { it.student.id == studentId }?.student
|
||||
val student = students.singleOrNull { it.id == studentId }
|
||||
when {
|
||||
student != null -> student
|
||||
studentId != 0L && studentRepository.isCurrentStudentSet() -> {
|
||||
@ -263,7 +274,10 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
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 accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId)
|
||||
|
@ -30,10 +30,6 @@ fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): Strin
|
||||
return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
|
||||
}
|
||||
|
||||
fun getRefreshKey(name: String): String {
|
||||
return name
|
||||
}
|
||||
|
||||
class AutoRefreshHelper @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val sharedPref: SharedPrefProvider
|
||||
|
@ -1,5 +1,8 @@
|
||||
Wersja 2.6.11
|
||||
Wersja 2.6.1
|
||||
|
||||
— naprawiliśmy obsługę wiadomości i ucznia plus u osób, u których działało na wcześniejszej wersji
|
||||
— 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
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="sort_alphabetically">По алфавиту</string>
|
||||
<string name="sort_by_date">По дате</string>
|
||||
<string name="sort_by_average">По средней</string>
|
||||
<string name="sort_by_attendance_percentage">Согласно проценту посещаемости</string>
|
||||
<string name="sort_by_subject_attendance_balance">Согласно балансу посещаемости уроков</string>
|
||||
<string name="sort_alphabetically">Alphabetically</string>
|
||||
<string name="sort_by_date">By date</string>
|
||||
<string name="sort_by_average">By average</string>
|
||||
<string name="sort_by_attendance_percentage">By attendance percentage</string>
|
||||
<string name="sort_by_subject_attendance_balance">By subject attendance balance</string>
|
||||
<string-array name="app_theme_entries" tools:ignore="InconsistentArrays">
|
||||
<item>Светлая</item>
|
||||
<item>Тёмная</item>
|
||||
@ -52,14 +52,14 @@
|
||||
<item>Средняя из оценок со всего года</item>
|
||||
</string-array>
|
||||
<string-array name="timetable_show_gaps_entries">
|
||||
<item>Не показывать</item>
|
||||
<item>Только между уроками</item>
|
||||
<item>Перед и между уроками</item>
|
||||
<item>Don\'t show</item>
|
||||
<item>Only between lessons</item>
|
||||
<item>Before and between lessons</item>
|
||||
</string-array>
|
||||
<string-array name="timetable_show_additional_lessons_entries">
|
||||
<item>Не показывать</item>
|
||||
<item>Показать в строке</item>
|
||||
<item>Показать ниже обычных уроков</item>
|
||||
<item>Don\'t show</item>
|
||||
<item>Show inline</item>
|
||||
<item>Show below regular lessons</item>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
<item>Счастливый номер</item>
|
||||
|
@ -13,7 +13,7 @@
|
||||
<string name="logviewer_title">Просмотр журнала</string>
|
||||
<string name="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="license_title">Лицензии</string>
|
||||
<string name="message_title">Сообщения</string>
|
||||
@ -38,14 +38,14 @@
|
||||
<string name="login_login_pesel_email_hint">Логин, PESEL или электронная почта</string>
|
||||
<string name="login_password_hint">Пароль</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_scrapper">Scraper</string>
|
||||
<string name="login_type_hybrid">Hybrid</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">Например: \"lodz\" или \"powiatjaroslawski\"</string>
|
||||
<string name="login_symbol_placeholder">E.g. \"lodz\" or \"powiatjaroslawski\"</string>
|
||||
<string name="login_sign_in">Войти</string>
|
||||
<string name="login_invalid_password">Пароль слишком короткий</string>
|
||||
<string name="login_incorrect_password_default">Данные для входа указаны неверно</string>
|
||||
@ -56,8 +56,8 @@
|
||||
<string name="login_invalid_email">Неверный 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_domain_suffix">Недопустимый суффикс домена</string>
|
||||
<string name="login_invalid_symbol">Неверный символ. Если вы не можете найти символ, пожалуйста, свяжитесь со школой</string>
|
||||
<string name="login_invalid_domain_suffix">Invalid domain suffix</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_incorrect_symbol">Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+</string>
|
||||
<string name="login_duplicate_student">Данный ученик уже авторизован</string>
|
||||
@ -73,7 +73,7 @@
|
||||
<string name="login_contact_discord">Discord</string>
|
||||
<string name="login_email_intent_title">Отправить письмо</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">Восстановить</string>
|
||||
<string name="login_signed_in">Ученик уже авторизован</string>
|
||||
@ -81,13 +81,13 @@
|
||||
<string name="login_other_search_locations">Другие места поиска</string>
|
||||
<string name="login_no_active_student">Не найдено активных учеников</string>
|
||||
<string name="login_symbol_enter">Введите другой symbol</string>
|
||||
<string name="login_support_title">Помощь</string>
|
||||
<string name="login_support_school_hint">Полное название школы с городом (обязательно)</string>
|
||||
<string name="login_support_school_placeholder">Например: ZSTiO Jarosław или SP nr 99 w Łodzi</string>
|
||||
<string name="login_support_school_invalid">Введите правильное название школы</string>
|
||||
<string name="login_support_additional_hint">Дополнительная информация на польском языке (опционально)</string>
|
||||
<string name="login_support_additional_placeholder">Например: \"Ostatnio zmieniłem szkołę i…\" или \"Jestem rodzicem i nie widzę drugiego dziecka…\"</string>
|
||||
<string name="login_support_submit">Отправить</string>
|
||||
<string name="login_support_title">Get help</string>
|
||||
<string name="login_support_school_hint">Full school name with the town (required)</string>
|
||||
<string name="login_support_school_placeholder">Np. ZSTiO Jarosław lub SP nr 99 w Łodzi</string>
|
||||
<string name="login_support_school_invalid">Enter correct name of the school</string>
|
||||
<string name="login_support_additional_hint">Additional information in Polish (optional)</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">Submit</string>
|
||||
<!--Notifications-->
|
||||
<string name="notifications_header_title">Включить уведомления</string>
|
||||
<string name="notifications_header_description">Включить уведомления, чтобы вы не пропустили сообщение от учителя или новую оценку</string>
|
||||
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Войти</string>
|
||||
<string name="main_session_expired">Сеанс истёк</string>
|
||||
<string name="main_session_relogin">Сеанс истёк, авторизуйтесь снова</string>
|
||||
<string name="main_expired_credentials_title">Срок действия пароля истек или был изменен</string>
|
||||
<string name="main_expired_credentials_description">Пароль вашей учетной записи устарел или был изменен. Вам нужно будет войти в Wulkanowy снова</string>
|
||||
<string name="main_expired_credentials_title">Password has expired or been changed</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_description">Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время</string>
|
||||
<string name="main_support_positive">Включить рекламу</string>
|
||||
@ -113,16 +113,16 @@
|
||||
<string name="grade_comment">Комментарий</string>
|
||||
<string name="grade_number_new_items">Количество новых оценок: %1$d</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_no_average">Нет средней оценки</string>
|
||||
<string name="grade_summary_average_semester">Средняя семестра</string>
|
||||
<string name="grade_summary_average_year">Средняя годовой</string>
|
||||
<string name="grade_summary_average_semester">Semester average</string>
|
||||
<string name="grade_summary_average_year">Annual average</string>
|
||||
<string name="grade_summary_points">Сумма баллов</string>
|
||||
<string name="grade_summary_final_grade">Итоговая оценка</string>
|
||||
<string name="grade_summary_predicted_grade">Ожидаемая оценка</string>
|
||||
<string name="grade_summary_descriptive">Описательная оценка</string>
|
||||
<string name="grade_summary_calculated_average">Рассчитанная средняя семестра</string>
|
||||
<string name="grade_summary_descriptive">Descriptive grade</string>
|
||||
<string name="grade_summary_calculated_average">Calculated semester average</string>
|
||||
<string name="grade_summary_calculated_average_annual">Calculated annual average</string>
|
||||
<string name="grade_summary_calculated_average_help_dialog_title">Как работает \"Рассчитанная средняя оценка\"?</string>
|
||||
<string name="grade_summary_calculated_average_help_dialog_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>
|
||||
</plurals>
|
||||
<plurals name="grade_new_items_descriptive">
|
||||
<item quantity="one">Новая описательная оценка</item>
|
||||
<item quantity="few">Новые описательные оценки</item>
|
||||
<item quantity="many">Новые описательные оценки</item>
|
||||
<item quantity="other">Новые описательные оценки</item>
|
||||
<item quantity="one">New descriptive grade</item>
|
||||
<item quantity="few">New descriptive grades</item>
|
||||
<item quantity="many">New descriptive grades</item>
|
||||
<item quantity="other">New descriptive grades</item>
|
||||
</plurals>
|
||||
<plurals name="grade_notify_new_items">
|
||||
<item quantity="one">Вы получили %1$d новую оценку</item>
|
||||
@ -191,14 +191,14 @@
|
||||
<item quantity="other">Вы получили %1$d новых итоговые оценки</item>
|
||||
</plurals>
|
||||
<plurals name="grade_notify_new_items_descriptive">
|
||||
<item quantity="one">Вы получили %1$d новую описательную оценку</item>
|
||||
<item quantity="few">Вы получили %1$d новых описательных оценок</item>
|
||||
<item quantity="many">Вы получили %1$d новых описательных оценок</item>
|
||||
<item quantity="other">Вы получили %1$d новых описательных оценок</item>
|
||||
<item quantity="one">You received %1$d descriptive grade</item>
|
||||
<item quantity="few">You received %1$d descriptive grades</item>
|
||||
<item quantity="many">You received %1$d descriptive grades</item>
|
||||
<item quantity="other">You received %1$d descriptive grades</item>
|
||||
</plurals>
|
||||
<!--Timetable-->
|
||||
<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_group">Группа</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_subject">Тема изменена с %1$s на %2$s</string>
|
||||
<plurals name="timetable_no_lesson">
|
||||
<item quantity="one">Нет урока</item>
|
||||
<item quantity="few">Нет урока</item>
|
||||
<item quantity="many">Нет урока</item>
|
||||
<item quantity="other">Нет урока</item>
|
||||
<item quantity="one">No lesson</item>
|
||||
<item quantity="few">No lessons</item>
|
||||
<item quantity="many">No lessons</item>
|
||||
<item quantity="other">No lessons</item>
|
||||
</plurals>
|
||||
<plurals name="timetable_notify_new_items_title">
|
||||
<item quantity="one">Изменение расписания</item>
|
||||
@ -270,7 +270,7 @@
|
||||
<string name="additional_lessons_end_time_error">Время окончания должно быть больше, чем время начала</string>
|
||||
<!--Attendance-->
|
||||
<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_neutral">right on 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_select_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_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_mailbox_type_student">ученик</string>
|
||||
<string name="message_mailbox_type_parent">родитель</string>
|
||||
@ -396,10 +396,10 @@
|
||||
<item quantity="other">%1$d выбрано</item>
|
||||
</plurals>
|
||||
<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_incognito_mode_on">Режим инкогнито включен</string>
|
||||
<string name="message_incognito_description">Благодаря режиму инкогнито отправитель не уведомлен о прочтении сообщения</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">Нет записей о замечаниях и свершениях</string>
|
||||
<string name="note_points">Баллы</string>
|
||||
@ -748,8 +748,8 @@
|
||||
<string name="pref_view_app_theme">Тема</string>
|
||||
<string name="pref_view_expand_grade">Разворачивание оценок</string>
|
||||
<string name="pref_view_timetable_show_groups">Показать группы рядом с темами</string>
|
||||
<string name="pref_view_timetable_show_additional_lessons">Показать дополнительные уроки</string>
|
||||
<string name="pref_view_timetable_show_gaps">Показать пустые поля, где нет уроков</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">Показывать диаграммы в оценках класса</string>
|
||||
<string name="pref_view_subjects_without_grades">Показать предметы без оценок</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_fill_message_content">Отвечать с историей сообщений</string>
|
||||
<string name="pref_other_optional_arithmetic_average">Показывать среднее арифметическое при отсутствии стоимости</string>
|
||||
<string name="pref_other_incognito_mode">Режим инкогнито</string>
|
||||
<string name="pref_other_incognito_mode_summary">Не сообщать о чтении сообщения</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">Поддержка</string>
|
||||
<string name="pref_ads_privacy_policy">Политика приватности</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_support">Посмотреть рекламу для поддержки проекта</string>
|
||||
<string name="pref_ads_privacy_title">Согласие на обработку данных</string>
|
||||
@ -813,8 +813,8 @@
|
||||
<string name="pref_dashboard_appearance_header">Главная</string>
|
||||
<string name="pref_dashboard_appearance_tiles_title">Видимость плиток</string>
|
||||
<string name="pref_attendance_appearance_view">Посещаемость</string>
|
||||
<string name="pref_attendance_calculator_appearance_view">Калькулятор посещаемости</string>
|
||||
<string name="pref_attendance_calculator_appearance_settings_title">Настройки</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">Расписание</string>
|
||||
<string name="pref_grades_advanced_header">Оценки</string>
|
||||
<string name="pref_counted_average_advanced_header">Рассчитанная средняя оценка</string>
|
||||
@ -866,31 +866,31 @@
|
||||
<string name="auth_button">Авторизовать</string>
|
||||
<string name="auth_success">Авторизация прошла успешно</string>
|
||||
<string name="auth_title">Авторизация</string>
|
||||
<string name="auth_description">Уважаемый родитель,<br /><br />для авторизации и обеспечения безопасности данных, просим Вас ввести ниже номер PESEL <b>%1$s</b>. Эти данные необходимы для надлежащего доступа к и защиты личных данных в соответствии с действующими нормами.<br /><br />После ввода данных мы обеспечим проверку, чтобы доступ к системе VULCAN был предоставлен исключительно уполномоченным лицам. Если у Вас возникли какие-либо сомнения или проблемы, пожалуйста, свяжитесь с администратором школьного дневника для уточнения ситуации.<br /><br />Мы соблюдаем наивысшие стандарты защиты персональных данных и гарантируем сохранность всей информации. Приложение Wulkanowy не сохраняет и не обрабатывает номер PESEL.<br /><br />Напоминаем, что предоставление точных данных является обязательным и необходимым для использования системы VULCAN.</string>
|
||||
<string name="auth_description">Dear Parent,<br /><br />To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br /><br />After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br /><br />We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br /><br />We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.</string>
|
||||
<string name="auth_button_skip">Пропустить сейчас</string>
|
||||
<!--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_verified_message">Верификация успешна</string>
|
||||
<string name="captcha_verified_message">Verified successfully</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Интернет-соединение отсутствует</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_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_service_unavailable">UONET+ проводит техническое обслуживание, повторите попытку позже</string>
|
||||
<string name="error_unknown_uonet">Неизвестная ошибка дневника UONET+, повторите попытку позже</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_feature_disabled">Функция отключена вашей школой</string>
|
||||
<string name="error_feature_not_available">Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом</string>
|
||||
<string name="error_field_required">Это поле обязательно</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Отключить уведомления</string>
|
||||
<string name="message_unmute">Включить уведомления</string>
|
||||
<string name="message_mute_success">Вы отключили уведомления от этого пользователя</string>
|
||||
<string name="message_unmute_success">Вы включили уведомления от этого пользователя снова</string>
|
||||
<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>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<string name="student_info_title">Student info</string>
|
||||
<string name="dashboard_title">Dashboard</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-->
|
||||
|
@ -8,7 +8,6 @@ import io.mockk.mockk
|
||||
|
||||
fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>()
|
||||
.apply {
|
||||
every { createBase() } returns sdk
|
||||
coEvery { create() } returns sdk
|
||||
every { create() } returns sdk
|
||||
coEvery { create(any(), any()) } returns sdk
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.sdk.pojo.RegisterStudent
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
@ -39,13 +40,11 @@ class WulkanowySdkFactoryTest {
|
||||
chuckerInterceptor = mockk(),
|
||||
remoteConfig = mockk(relaxed = true),
|
||||
webkitCookieManagerProxy = mockk(),
|
||||
studentDb = studentDao,
|
||||
wulkanowyRepository = mockk(relaxed = true),
|
||||
context = mockk(),
|
||||
studentDb = studentDao
|
||||
)
|
||||
)
|
||||
|
||||
coEvery { wulkanowySdkFactory.create() } returns sdk
|
||||
every { wulkanowySdkFactory.create() } returns sdk
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -12,9 +12,7 @@ import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@HiltAndroidTest
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@ -22,9 +20,10 @@ import kotlin.test.assertTrue
|
||||
class Migration63Test : AbstractMigrationTest() {
|
||||
|
||||
@Test
|
||||
fun `update is_edu_one to null if 0`() = runTest {
|
||||
fun `update is_edu_one to null`() = runTest {
|
||||
with(helper.createDatabase(dbName, 62)) {
|
||||
createStudent(1, 0)
|
||||
createStudent(2, 1)
|
||||
close()
|
||||
}
|
||||
|
||||
@ -32,31 +31,15 @@ class Migration63Test : AbstractMigrationTest() {
|
||||
|
||||
val database = getMigratedRoomDatabase()
|
||||
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()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check is_edu_one is stay same`() = runTest {
|
||||
with(helper.createDatabase(dbName, 62)) {
|
||||
createStudent(1, 1)
|
||||
close()
|
||||
}
|
||||
|
||||
helper.runMigrationsAndValidate(dbName, 63, true)
|
||||
|
||||
val database = getMigratedRoomDatabase()
|
||||
val studentDb = database.studentDao
|
||||
val student = studentDb.loadById(1)
|
||||
|
||||
val isEduOne = assertNotNull(student!!.isEduOne)
|
||||
assertTrue(isEduOne)
|
||||
database.close()
|
||||
}
|
||||
|
||||
private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) {
|
||||
insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
|
||||
put("scrapper_base_url", "https://fakelog.cf")
|
||||
|
@ -50,7 +50,7 @@ class SemesterRepositoryTest {
|
||||
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 { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
@ -77,10 +77,7 @@ class SemesterRepositoryTest {
|
||||
)
|
||||
|
||||
coEvery {
|
||||
semesterDb.loadAll(
|
||||
student.studentId,
|
||||
student.classId
|
||||
)
|
||||
semesterDb.loadAll(student)
|
||||
} returns badSemesters.mapToEntities(student.studentId)
|
||||
coEvery { sdk.getSemesters() } returns goodSemesters
|
||||
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
@ -103,7 +100,7 @@ class SemesterRepositoryTest {
|
||||
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),
|
||||
goodSemesters.mapToEntities(student.studentId)
|
||||
@ -125,7 +122,7 @@ class SemesterRepositoryTest {
|
||||
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) }
|
||||
assertEquals(2, items.size)
|
||||
@ -138,7 +135,7 @@ class SemesterRepositoryTest {
|
||||
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) }
|
||||
assertEquals(2, items.size)
|
||||
@ -151,7 +148,7 @@ class SemesterRepositoryTest {
|
||||
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) }
|
||||
assertEquals(2, items.size)
|
||||
@ -164,7 +161,7 @@ class SemesterRepositoryTest {
|
||||
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 { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
@ -194,10 +191,7 @@ class SemesterRepositoryTest {
|
||||
)
|
||||
|
||||
coEvery {
|
||||
semesterDb.loadAll(
|
||||
student.studentId,
|
||||
student.classId
|
||||
)
|
||||
semesterDb.loadAll(student)
|
||||
} returns semestersWithNoCurrent
|
||||
coEvery { sdk.getSemesters() } returns newSemesters
|
||||
coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
@ -214,7 +208,7 @@ class SemesterRepositoryTest {
|
||||
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)
|
||||
assertEquals(2, items.size)
|
||||
@ -227,14 +221,14 @@ class SemesterRepositoryTest {
|
||||
getSemesterEntity(1, 1, now(), now())
|
||||
)
|
||||
|
||||
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters
|
||||
coEvery { semesterDb.loadAll(student) } returns semesters
|
||||
|
||||
runBlocking { semesterRepository.getCurrentSemester(student) }
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException::class)
|
||||
fun getCurrentSemester_emptyList() {
|
||||
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList()
|
||||
coEvery { semesterDb.loadAll(student) } returns emptyList()
|
||||
coEvery { sdk.getSemesters() } returns emptyList()
|
||||
|
||||
runBlocking { semesterRepository.getCurrentSemester(student) }
|
||||
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.main
|
||||
import io.github.wulkanowy.MainCoroutineRule
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
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.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AdsHelper
|
||||
@ -32,9 +31,6 @@ class MainPresenterTest {
|
||||
@MockK
|
||||
lateinit var studentRepository: StudentRepository
|
||||
|
||||
@MockK(relaxed = true)
|
||||
lateinit var wulkanowyRepository: WulkanowyRepository
|
||||
|
||||
@MockK(relaxed = true)
|
||||
lateinit var prefRepository: PreferencesRepository
|
||||
|
||||
@ -69,8 +65,7 @@ class MainPresenterTest {
|
||||
analytics = analytics,
|
||||
json = Json,
|
||||
appInfo = appInfo,
|
||||
adsHelper = adsHelper,
|
||||
wulkanowyRepository = wulkanowyRepository
|
||||
adsHelper = adsHelper
|
||||
)
|
||||
presenter.onAttachView(mainView, null)
|
||||
}
|
||||
|
Reference in New Issue
Block a user