sync with 2.6.12 #6

Merged
sadorowo merged 16 commits from release/2.6.12 into develop 2024-05-23 07:56:16 -05:00
17 changed files with 281 additions and 159 deletions

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 34
versionCode 168
versionName "2.6.8"
versionCode 172
versionName "2.6.12"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -187,11 +187,11 @@ 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.7'
implementation 'io.github.wulkanowy:sdk:2.6.10'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'

View File

@ -59,7 +59,7 @@ class WulkanowySdkFactory @Inject constructor(
if (mapping != null) {
endpointsMapping = mapping.endpoints
vTokenMapping = mapping.vTokens
vTokenSchemeMapping = mapping.vTokenScheme
vHeaders = mapping.vHeaders
vParamsEvaluation = createIsolate()
}
}

View File

@ -14,4 +14,7 @@ data class Mapping(
@SerialName("vTokenScheme")
val vTokenScheme: Map<String, Map<String, String>> = emptyMap(),
@SerialName("vHeaders")
val vHeaders: Map<String, Map<String, Map<String, String>>> = emptyMap(),
)

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,6 +1,5 @@
Wersja 2.6.8
Wersja 2.6.12
— i znowu aktualizacja, żeby wiadomości działały chociaż w piątek. Później już pewnie nie będą
— a i jeszcze naprawiliśmy dostęp mobilny, bo prosiliście
— 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>