Compare commits

...

36 Commits

Author SHA1 Message Date
7266d1fcd3 merge: v2.6.12 + cache fix (#7)
Reviewed-on: #7
2024-05-23 09:08:17 -05:00
e048fe4c35 Merge branch 'develop' into release/2.6.12
All checks were successful
Generate APK / build (pull_request) Successful in 34m25s
2024-05-23 09:07:44 -05:00
49541e9a7d chore: updated workflow 2024-05-23 16:07:23 +02:00
72c93594f6 merge: v2.6.12 (#6)
Reviewed-on: #6
2024-05-23 07:56:14 -05:00
b249eab1eb bump version to 2.6.12
Some checks failed
Generate APK / build (pull_request) Has been cancelled
2024-05-23 14:53:15 +02:00
c7afa262d9 Merge branch 'release/2.6.12' into develop 2024-05-23 07:55:17 +02:00
b622c09e56 Version 2.6.12 2024-05-23 07:55:02 +02:00
87fb1916d8 Add panic button 2024-05-23 07:37:49 +02:00
5ca9ac3978 Version 2.6.11 2024-05-22 22:05:11 +02:00
c76ace40eb Fix mapping refresh key 2024-05-22 21:05:17 +02:00
4a585fc56e Fix mapping refresh key 2024-05-22 21:00:09 +02:00
f3afe7fdb7 Bump sdk to 2.6.10-SNAPSHOT 2024-05-22 20:31:40 +02:00
859c6ef154 Add auto refresh to wulkanowy repo 2024-05-22 20:23:57 +02:00
24abe47332 Version 2.6.10 2024-05-22 19:26:01 +02:00
52c1878f6b Bump sdk to 2.6.9-SNAPSHOT 2024-05-22 19:26:01 +02:00
4060240368 New Crowdin updates (#2551) 2024-05-17 22:22:43 +02:00
f69816fbac Bump coroutines from 1.8.0 to 1.8.1 (#2560) 2024-05-17 20:21:52 +00:00
1a41e9e3ee Merge branch 'release/2.6.9' into develop 2024-05-17 21:23:40 +02:00
1c0df6c145 Version 2.6.9 2024-05-17 21:23:29 +02:00
2b61e883c5 Bump sdk to 2.6.8-SNAPSHOT 2024-05-17 19:45:53 +02:00
d29d95bfae create v2.6.8 release
Reviewed-on: #5
2024-05-17 02:09:31 -05:00
65a0457675 merge latest develop branch 2024-05-17 09:08:33 +02:00
b25c61a485 merge: v2.6.8 2024-05-17 09:08:20 +02:00
e33350e153 Merge branch 'develop' into release/2.6.8
All checks were successful
Generate APK / build (pull_request) Successful in 1h9m54s
2024-05-17 02:07:44 -05:00
8109459ee2 merge: v2.6.8 2024-05-17 09:04:58 +02:00
31a7ae6d15 Fix tests 2024-05-17 08:04:28 +02:00
4b3b4a21fa Merge branch 'release/2.6.8' into develop 2024-05-17 07:39:16 +02:00
9a6b17c9d9 Version 2.6.8 2024-05-17 07:38:13 +02:00
729e0f547b Add sandbox isolate 2024-05-16 23:23:06 +02:00
faa8d34e79 Bump sdk to 2.6.7-SNAPSHOT 2024-05-16 18:55:02 +02:00
e553d9cdcf merge: v2.6.7 (#4)
Reviewed-on: #4
2024-05-16 08:18:37 -05:00
b6f5ac91ad Merge branch 'release/2.6.7' into develop 2024-05-16 08:50:37 +02:00
d1d0caa1e3 Version 2.6.7 2024-05-16 08:50:28 +02:00
eed3a93196 merge: v2.6.7
All checks were successful
Generate APK / build (pull_request) Successful in 1h1m19s
2024-05-15 15:15:10 +02:00
c40779f48f Merge branch 'release/2.6.6' into develop 2024-05-14 22:31:01 +02:00
594d2dbec5 Version 2.6.6 2024-05-14 22:30:51 +02:00
22 changed files with 325 additions and 165 deletions

View File

@ -10,7 +10,6 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
@ -29,7 +28,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'zulu' # See 'Supported distributions' for available options
distribution: 'zulu'
java-version: '17'
cache: 'gradle'
@ -55,7 +54,7 @@ jobs:
- name: Get app version
id: get_version
run: echo "VERSION_NAME=$(grep -m1 "versionName" app/build.gradle | awk '{print $2}' | tr -d \''"\')" >> $GITHUB_ENV
run: echo "VERSION_NAME=$(grep -m1 "versionName" app/build.gradle | awk '{print $2}' | tr -d \'\'\"\')" >> $GITHUB_ENV
- name: Change wrapper permissions
run: chmod +x ./gradlew

1
.gitignore vendored
View File

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

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
versionCode 165
versionName "2.6.5"
versionCode 172
versionName "2.6.12"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -161,7 +161,7 @@ play {
track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS
userFraction = 0.99d
updatePriority = 3
updatePriority = 2
enabled.set(false)
}
@ -187,16 +187,17 @@ ext {
room = "2.6.1"
chucker = "4.0.0"
mockk = "1.13.10"
coroutines = "1.8.0"
coroutines = "1.8.1"
}
dependencies {
implementation 'io.github.wulkanowy:sdk:2.6.4'
implementation 'io.github.wulkanowy:sdk:2.6.10'
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'
@ -204,6 +205,7 @@ 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"

View File

@ -3,6 +3,8 @@
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" />
@ -42,9 +44,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"

View File

@ -1,14 +1,21 @@
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
@ -17,6 +24,7 @@ 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,
@ -26,10 +34,14 @@ class WulkanowySdkFactory @Inject constructor(
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 = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
androidVersion = Build.VERSION.RELEASE
buildTag = Build.MODEL
userAgentTemplate = remoteConfig.userAgentTemplate
setSimpleHttpLogger { Timber.d(it) }
setAdditionalCookieManager(webkitCookieManagerProxy)
@ -47,6 +59,23 @@ class WulkanowySdkFactory @Inject constructor(
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()
}
}
}
}

View File

@ -11,4 +11,10 @@ data class Mapping(
@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(),
)

View File

@ -11,6 +11,6 @@ interface WulkanowyService {
@GET("/v1.json")
suspend fun getAdminMessages(): List<AdminMessage>
@GET("/mapping1.json")
@GET("/mapping2.json")
suspend fun getMapping(): Mapping
}

View File

@ -6,6 +6,8 @@ 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
@ -18,10 +20,13 @@ 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,
@ -38,7 +43,11 @@ class WulkanowyRepository @Inject constructor(
suspend fun getMapping(): Mapping? {
var savedMapping = preferencesRepository.mapping
if (savedMapping == null) {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey)
)
if (savedMapping == null || isExpired) {
fetchMapping()
savedMapping = preferencesRepository.mapping
}
@ -49,6 +58,9 @@ class WulkanowyRepository @Inject constructor(
suspend fun fetchMapping() {
runCatching { wulkanowyService.getMapping() }
.onFailure { Timber.e(it) }
.onSuccess { preferencesRepository.mapping = it }
.onSuccess {
preferencesRepository.mapping = it
refreshHelper.updateLastRefreshTimestamp(cacheKey)
}
}
}

View File

@ -125,6 +125,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
mainActivity.pushView(ConferenceFragment.newInstance())
}
onAdminMessageClickListener = presenter::onAdminMessageSelected
onPanicButtonClickListener = presenter::onPanicButtonClicked
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
@ -208,7 +209,11 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
binding = binding.dashboardErrorAdminMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected,
).bind(adminMessageItem.adminMessage)
onPanicButtonClickListener = presenter::onPanicButtonClicked,
).bind(
item = adminMessageItem.adminMessage,
showPanicButton = true,
)
}
}

View File

@ -11,6 +11,7 @@ import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.mapResourceData
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.ConferenceRepository
import io.github.wulkanowy.data.repositories.ExamRepository
@ -23,6 +24,7 @@ import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase
import io.github.wulkanowy.ui.base.BasePresenter
@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber
import java.time.Instant
import java.time.LocalDate
@ -282,6 +285,22 @@ class DashboardPresenter @Inject constructor(
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?.openInternetBrowser(urlToOpen)
}
.launch("panic_button")
}
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow {
val attendancePercentage = preferencesRepository.attendancePercentage

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,10 @@ 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

View File

@ -1,5 +1,5 @@
Wersja 2.6.5
Wersja 2.6.12
naprawiliśmy pozostałe bieżące problemy spowodowane ostatnimi blokadami Wulkanowego przez wiadomo kogo
dodaliśmy przycisk otwierający stronę dziennika w awaryjnych sytuacjach (ostatnio cały czas jest awaryjna sytuacja)
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -1,87 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
<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="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp">
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dashboard_admin_message_item_content"
<com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_admin_message"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
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" />
<TextView
android:id="@+id/dashboard_admin_message_item_title"
android:layout_width="0dp"
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" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dashboard_admin_message_item_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/dashboard_admin_message_item_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:padding="12dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<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" />
<TextView
android:id="@+id/dashboard_admin_message_item_description"
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" />
<TextView
android:id="@+id/dashboard_admin_message_item_title"
android:layout_width="0dp"
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" />
<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>
<ImageView
android:id="@+id/dashboard_admin_message_item_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:background="?selectableItemBackgroundBorderless"
android:padding="12dp"
android:src="@drawable/ic_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/dashboard_admin_message_item_description"
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

@ -0,0 +1,28 @@
<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

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<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 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-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>Don\'t show</item>
<item>Only between lessons</item>
<item>Before and between lessons</item>
<item>Не показывать</item>
<item>Только между уроками</item>
<item>Перед и между уроками</item>
</string-array>
<string-array name="timetable_show_additional_lessons_entries">
<item>Don\'t show</item>
<item>Show inline</item>
<item>Show below regular lessons</item>
<item>Не показывать</item>
<item>Показать в строке</item>
<item>Показать ниже обычных уроков</item>
</string-array>
<string-array name="dashboard_tile_entries">
<item>Счастливый номер</item>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Activity/Fragment title-->
<string name="login_title">Авторизация</string>
@ -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">Clear webview cookies</string>
<string name="debug_cookies_clear">Очистить файлы cookie</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">Custom domain suffix</string>
<string name="login_domain_suffix_hint">Пользовательский суффикс домена</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">E.g. \"lodz\" or \"powiatjaroslawski\"</string>
<string name="login_symbol_placeholder">Например: \"lodz\" или \"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">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_domain_suffix">Недопустимый суффикс домена</string>
<string name="login_invalid_symbol">Неверный символ. Если вы не можете найти символ, пожалуйста, свяжитесь со школой</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">Reset password</string>
<string name="login_recover_button">Сбросить пароль</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">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>
<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>
<!--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">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_expired_credentials_title">Срок действия пароля истек или был изменен</string>
<string name="main_expired_credentials_description">Пароль вашей учетной записи устарел или был изменен. Вам нужно будет войти в Wulkanowy снова</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">Annual: %1$.2f</string>
<string name="grade_average_year">Годовое: %1$.2f</string>
<string name="grade_points_sum">Баллы: %s</string>
<string name="grade_no_average">Нет средней оценки</string>
<string name="grade_summary_average_semester">Semester average</string>
<string name="grade_summary_average_year">Annual average</string>
<string name="grade_summary_average_semester">Средняя семестра</string>
<string name="grade_summary_average_year">Средняя годовой</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">Descriptive grade</string>
<string name="grade_summary_calculated_average">Calculated semester average</string>
<string name="grade_summary_descriptive">Описательная оценка</string>
<string name="grade_summary_calculated_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">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>
<item quantity="one">Новая описательная оценка</item>
<item quantity="few">Новые описательные оценки</item>
<item quantity="many">Новые описательные оценки</item>
<item quantity="other">Новые описательные оценки</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">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>
<item quantity="one">Вы получили %1$d новую описательную оценку</item>
<item quantity="few">Вы получили %1$d новых описательных оценок</item>
<item quantity="many">Вы получили %1$d новых описательных оценок</item>
<item quantity="other">Вы получили %1$d новых описательных оценок</item>
</plurals>
<!--Timetable-->
<string name="timetable_lesson">Урок</string>
<string name="timetable_additional_lesson">Additional lesson</string>
<string name="timetable_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">No lesson</item>
<item quantity="few">No lessons</item>
<item quantity="many">No lessons</item>
<item quantity="other">No lessons</item>
<item quantity="one">Нет урока</item>
<item quantity="few">Нет урока</item>
<item quantity="many">Нет урока</item>
<item quantity="other">Нет урока</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">Attendance calculator</string>
<string name="attendance_calculator_button">Калькулятор посещаемости</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">Restore from trash</string>
<string name="message_restore_from_trash">Восстановить из корзины</string>
<string name="message_move_to_trash">Перенести в корзину</string>
<string name="message_delete_forever">Удалить навсегда</string>
<string name="message_restore_success">Message restored successfully</string>
<string name="message_restore_success">Сообщение успешно восстановлено</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">Messages restored</string>
<string name="message_messages_restored">Сообщения восстановлены</string>
<string name="message_mailbox_chooser_title">Выбрать почтовый ящик</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>
<string name="message_incognito_mode_on">Режим инкогнито включен</string>
<string name="message_incognito_description">Благодаря режиму инкогнито отправитель не уведомлен о прочтении сообщения</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">Show additional lessons</string>
<string name="pref_view_timetable_show_gaps">Show empty tiles where there\'s no lesson</string>
<string name="pref_view_timetable_show_additional_lessons">Показать дополнительные уроки</string>
<string name="pref_view_timetable_show_gaps">Показать пустые поля, где нет уроков</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">Incognito mode</string>
<string name="pref_other_incognito_mode_summary">Do not inform about reading the message</string>
<string name="pref_other_incognito_mode">Режим инкогнито</string>
<string name="pref_other_incognito_mode_summary">Не сообщать о чтении сообщения</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">Show consent to data processing</string>
<string name="pref_ads_consent">Показать согласие на обработку данных</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">Attendance calculator</string>
<string name="pref_attendance_calculator_appearance_settings_title">Settings</string>
<string name="pref_attendance_calculator_appearance_view">Калькулятор посещаемости</string>
<string name="pref_attendance_calculator_appearance_settings_title">Настройки</string>
<string name="pref_timetable_appearance_view">Расписание</string>
<string name="pref_grades_advanced_header">Оценки</string>
<string name="pref_counted_average_advanced_header">Рассчитанная средняя оценка</string>
@ -866,32 +866,32 @@
<string name="auth_button">Авторизовать</string>
<string name="auth_success">Авторизация прошла успешно</string>
<string name="auth_title">Авторизация</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_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_button_skip">Пропустить сейчас</string>
<!--Captcha-->
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
<string name="captcha_dialog_title">Требуется верификация веб-сайта VULCAN</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">Верификация успешна</string>
<!--Errors-->
<string name="error_no_internet">Интернет-соединение отсутствует</string>
<string name="error_invalid_device_datetime">Произошла ошибка. Проверьте время на вашем устройстве</string>
<string name="error_account_inactive">This account is inactive. Try logging in again</string>
<string name="error_account_inactive">Эта учетная запись неактивна. Попробуйте войти снова</string>
<string name="error_timeout">Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже</string>
<string name="error_login_failed">Не удалось загрузить данные, повторите попытку позже</string>
<string name="error_password_invalid">Your password has expired or been changed. Please log in again</string>
<string name="error_password_invalid">Ваш пароль устарел или был изменен. Пожалуйста, войдите снова</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">Captcha verification required</string>
<string name="error_cloudflare_captcha">Требуется подтверждение капчи</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">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>
<string name="pref_mod_settings_other_title">Прочее</string>
<string name="message_mute">Отключить уведомления</string>
<string name="message_unmute">Включить уведомления</string>
<string name="message_mute_success">Вы отключили уведомления от этого пользователя</string>
<string name="message_unmute_success">Вы включили уведомления от этого пользователя снова</string>
>>>>>>> upstream/develop
</resources>

View File

@ -41,6 +41,7 @@ class WulkanowySdkFactoryTest {
webkitCookieManagerProxy = mockk(),
studentDb = studentDao,
wulkanowyRepository = mockk(relaxed = true),
context = mockk(),
)
)