diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 4653d27a9..c03ccedb6 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -106,4 +106,4 @@ jobs: # Publish # -------------------------------------- - name: Publish - run: ./gradlew publishReleaseBundle + run: ./gradlew :app:publishReleaseBundle :app:publishReleaseListing diff --git a/.run/Generate screenshots.run.xml b/.run/Generate screenshots.run.xml new file mode 100644 index 000000000..54fbace3a --- /dev/null +++ b/.run/Generate screenshots.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + diff --git a/AGENTS.md b/AGENTS.md index d95d6a6fb..1c15577eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,11 +9,19 @@ * **Сборка:** Gradle Kotlin DSL, Version Catalogs (`libs.versions.toml`). * **Архитектура:** Многомодульная (`app`, `features/*`, `core/*`, `common/*`). -## 2. Терминология -* **Lesson**: В коде и комментариях переводить как **"Занятие"**, а не "Урок". Это универсальный термин для школы и - вуза. -* **Semester**: Семестр или четверть. -* **Homework**: Домашнее задание. +## 2. Терминология и Локализация +Проект ориентирован одновременно на **студентов и школьников**. Тексты и термины должны быть максимально универсальными. + +* **Lesson (Сущность):** + * В коде: `Lesson`. + * В UI (RU): Используйте термин **«Занятие»** (нейтральное) как основной. Термин «Урок» допускается только как опция + в выпадающих списках. + * В UI (EN): Используйте **"Class"** как основной термин. "Lesson" — только как опция. +* **Semester (Сущность):** + * В коде: `Semester`. + * В UI: Используйте термин **«Расписание»** (Schedule). Избегайте слов «Семестр» или «Четверть» в заголовках и общих + описаниях, так как они специфичны для конкретных учебных заведений. +* **Homework:** Домашнее задание. ## 3. Архитектура и Многомодульность @@ -327,6 +335,8 @@ UI. * **Подключение:** Для использования аналитики в фиче необходимо добавить зависимость `implementation(project(":features:analytics:api"))` в `build.gradle.kts` и обновить интерфейс зависимостей модуля. +* **Анонимность:** Сбор данных производится **полностью анонимно**. Сбор рекламного идентификатора (AAID) технически + отключен. ### 14.2. Именование (Naming Conventions) Мы используем стандартные соглашения Firebase/Google Analytics. @@ -367,3 +377,37 @@ ### 14.4. Свойства пользователя (User Properties) Используйте `setUserProperty` для атрибутов, которые редко меняются и описывают сегмент пользователя. * Примеры: `theme` (light/dark), `is_advanced_weeks_selector_enabled` (true/false). + +### 14.5. Ограничения данных (Демография) +Из-за отключения AAID (см. раздел 15) в аналитике **отсутствуют** данные о: +* Пол и возраст (Demographics). +* Интересы (Interests). +* Google Signals (Cross-device tracking). + +Это нормальное поведение. Не пытайтесь "починить" это включением AAID. + +## 15. Требования Google Play и Безопасность + +Приложение официально таргетируется на смешанную аудиторию (включая детей от 6 лет) и участвует в программе **Designed +for Families**. Это накладывает строгие ограничения на код и конфигурацию. + +### 15.1. Запрет на Рекламные ID (AAID) +**Строго запрещено** включать сбор рекламного идентификатора или добавлять соответствующие разрешения. Это приведет к +бану приложения. + +**Конфигурация в `AndroidManifest.xml` (`:app`):** +1. Явное удаление разрешений с помощью `tools:node="remove"`: + * `com.google.android.gms.permission.AD_ID` + * `android.permission.ACCESS_ADSERVICES_AD_ID` + * `android.permission.ACCESS_ADSERVICES_ATTRIBUTION` +2. Метаданные для отключения в Firebase: + * `google_analytics_adid_collection_enabled = false` + * `google_analytics_default_allow_ad_personalization_signals = false` + +Не удаляйте эти строки и комментарии к ним. + +### 15.2. Политика конфиденциальности +* Файл: `PRIVACY_POLICY.md` в корне проекта. +* Содержание: Должно честно отражать работу приложения (локальная БД, отсутствие рекламы, анонимная аналитика). +* При добавлении новых SDK (например, для аналитики или сбоев) обязательно обновляйте этот файл и проверяйте их на + соответствие семейной политике (никакого сбора PII или AAID). diff --git a/CHANGELOG.md b/CHANGELOG.md index 17cd34f05..595f48994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Улучшение и исправление текстовок на всех языках + ## [0.8.0] - 2026-01-03 ### Fixed diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md new file mode 100644 index 000000000..663f3139d --- /dev/null +++ b/PRIVACY_POLICY.md @@ -0,0 +1,82 @@ +# Privacy Policy + +**Last updated:** January 10, 2026 + +Erdenian Apps built the Student Assistant app as an Open Source app. This Service is provided by Erdenian Apps at no +cost and is intended for use as is. + +This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal +Information if anyone decided to use our Service. + +By using the Student Assistant app, you agree to the collection and use of information in relation to this policy. + +## 1. Information Collection and Use + +### A. Local Data (Offline Storage) +The core functionality of the App is designed to work **offline**. +* **User Content:** Data you enter into the App (including schedules, subject names, teacher names, and homework + descriptions) is stored strictly locally on your device using a local database. +* **No Cloud Sync:** We do not transmit your schedule or homework data to our servers or any third-party servers. +* **Data Deletion:** The information that you enter is retained on your device and is not collected by us in any way. If + you uninstall the App or clear its data via system settings, all your local data will be permanently deleted. + +### B. Telemetry and Analytics (Anonymous) +We use third-party services to improve the stability and usability of our App. These services may collect information +used to identify your device, but **not** your identity. + +**Services used:** +1. **Google Analytics for Firebase:** Used to analyze usage patterns (e.g., screen views, button clicks) to improve the + user experience. +2. **Firebase Crashlytics:** Used to collect crash reports ("Log Data"). This data may include information such as your + device Internet Protocol ("IP") address, device name, operating system version, the configuration of the app when + utilizing our Service, the time and date of your use of the Service, and other statistics. + +**Important regarding Advertising IDs:** +To ensure the privacy of all our users, including children: +* We have **disabled** the collection of the Android Advertising ID (AAID). +* We have **disabled** ad personalization signals. +* We do not use this data for marketing or advertising purposes. + +## 2. Third-Party Service Providers + +We may employ third-party companies and individuals to facilitate our Service, provide the Service on our behalf, or +assist us in analyzing how our Service is used. + +Links to the privacy policy of third-party service providers used by the app: +* [Google Play Services](https://www.google.com/policies/privacy/) +* [Google Analytics for Firebase](https://firebase.google.com/policies/analytics) +* [Firebase Crashlytics](https://firebase.google.com/support/privacy/) + +## 3. Cookies + +Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. This Service does +not use these "cookies" explicitly. However, the app may use third-party code and libraries that use "cookies" to +collect information and improve their services. You have the option to either accept or refuse these cookies and know +when a cookie is being sent to your device. + +## 4. Children’s Privacy + +Our App is designed to be useful for students of all ages, including children under the age of 13. We do not knowingly +collect personally identifiable information from children under 13 years of age. + +Since the App's main data (schedule/homework) is stored locally and not shared with us, and the analytics data is +anonymized (without Advertising ID), we ensure a safe environment. + +If you are a parent or guardian and you are aware that your child has provided us with Personal Information (outside of +the standard anonymous telemetry), please contact us so that we will be able to do necessary actions. + +## 5. Security + +We value your trust in providing us your Personal Information (even if it is stored locally), thus we are striving to +use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or +method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security. + +## 6. Changes to This Privacy Policy + +We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any +changes. We will notify you of any changes by posting the new Privacy Policy on this page. + +## 7. Contact Us + +If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us at +**erdenianapps@gmail.com**. diff --git a/README.md b/README.md index 9f757ef80..bf6f93ff0 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ ## Основные функции ### Расписание -- Создание и редактирование расписания на семестр +- Создание и редактирование расписания - Добавление занятий: предмет, преподаватель, аудитория, время - Гибкая настройка повторений (по дням недели, по датам, чередование недель) -- Поддержка нескольких расписаний (семестров) +- Поддержка нескольких расписаний - Календарь для навигации по датам ### Домашние задания @@ -22,7 +22,7 @@ ### Настройки - Установка времени начала первого занятия -- Настройка длительности уроков и перерывов +- Настройка длительности занятий и перерывов - Включение расширенного режима выбора недель --- @@ -47,7 +47,7 @@ app/ — Основной модуль приложения, связывание компонентов features/ -├── schedule/ — Экран расписания, редактор занятий и семестров +├── schedule/ — Экран расписания, редактор расписания ├── homeworks/ — Экран домашних заданий и их редактор ├── settings/ — Экран настроек └── repository/ — Слой данных (Room Database, DAO, реализации репозиториев) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b15d6db4d..ff376eb52 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ @file:Suppress("UnstableApiUsage") import java.time.LocalDate +import org.gradle.internal.extensions.stdlib.capitalized plugins { alias(libs.plugins.android.application) @@ -225,3 +226,175 @@ rootProject.tasks.register("updateChangelog") { } // endregion + +// region Screenshots + +abstract class GenerateScreenshotsTask : DefaultTask() { + + @get:Input + abstract val adbPath: Property + + @get:Input + abstract val appPackage: Property + + @get:Input + abstract val testPackage: Property + + @get:Input + abstract val testRunner: Property + + @get:Input + abstract val testClass: Property + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val sources: ConfigurableFileTree + + @get:Internal + abstract val tempDir: DirectoryProperty + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @get:Inject + abstract val execOperations: ExecOperations + + @get:Inject + abstract val fs: FileSystemOperations + + init { + group = "android" + description = "Generates screenshots for all supported locales using an automated test." + } + + @TaskAction + fun run() { + val adb = adbPath.get() + val pkg = appPackage.get() + val deviceDir = "/sdcard/Android/data/$pkg/files/screenshots" + val localTemp = tempDir.get().asFile + val localFinal = outputDir.get().asFile + + // Вспомогательная функция для запуска adb + fun adb(vararg args: String) { + val stdout = `java.io`.ByteArrayOutputStream() + execOperations.exec { + executable = adb + args(*args) + standardOutput = stdout + } + val output = stdout.toString() + println(output) + + // Дополнительно проверяем есть ли проваленные тесты + if (output.contains("FAILURES!!!")) { + throw GradleException("Instrumentation tests failed. See output above.") + } + } + + fun shell(vararg command: String) { + adb("shell", *command) + } + + println("=== 1. Очистка старых скриншотов на устройстве ===") + shell("rm", "-rf", deviceDir) + + // @formatter:off + println("=== 2. Настройка Demo Mode и отключение анимаций ===") + // Demo Mode + shell("settings", "put", "global", "sysui_demo_allowed", "1") + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "enter") + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "clock", "-e", "hhmm", "1400") + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "network", "-e", "mobile", "show", "-e", "level", "4", "-e", "datatype", "lte") + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "network", "-e", "wifi", "show", "-e", "level", "4", "-e", "fully", "true") + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "battery", "-e", "level", "100", "-e", "plugged", "false") + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "notifications", "-e", "visible", "false") + + // Отключение анимаций (0 = выкл) + shell("settings", "put", "global", "window_animation_scale", "0") + shell("settings", "put", "global", "transition_animation_scale", "0") + shell("settings", "put", "global", "animator_duration_scale", "0") + + println("=== 3. Запуск теста генерации скриншотов ===") + // Передаем аргумент is_screenshot_mode=true + shell("am", "instrument", "-w", "-r", "-e", "class", testClass.get(), "-e", "is_screenshot_mode", "true", "${testPackage.get()}/${testRunner.get()}") + // @formatter:on + + println("=== 4. Выключение Demo Mode и включение анимаций ===") + // Включение анимаций (1 = вкл) + shell("settings", "put", "global", "window_animation_scale", "1") + shell("settings", "put", "global", "transition_animation_scale", "1") + shell("settings", "put", "global", "animator_duration_scale", "1") + + shell("am", "broadcast", "-a", "com.android.systemui.demo", "-e", "command", "exit") + + println("=== 5. Копирование скриншотов в проект ===") + // Очищаем локальную временную папку + localTemp.deleteRecursively() + localTemp.mkdirs() + + adb("pull", "$deviceDir/.", localTemp.absolutePath) + + localTemp.listFiles()?.forEach { langDir -> + if (!langDir.isDirectory) return@forEach + + val langCode = langDir.name + val targetDir = localFinal.resolve("$langCode/graphics/phone-screenshots") + + println("Processing $langCode -> $targetDir") + targetDir.mkdirs() + + // Удаляем старые png + targetDir.listFiles { it.extension == "png" }?.forEach { it.delete() } + + // Копируем новые + fs.copy { + from(langDir) + into(targetDir) + include("*.png") + } + } + + println("=== 6. Очистка временных файлов ===") + localTemp.deleteRecursively() + + println("=== Готово! Скриншоты обновлены. ===") + } +} + +tasks.register("generateScreenshots") { + val buildType = "debug" + + dependsOn(tasks.named("install${buildType.capitalized()}")) + dependsOn(tasks.named("install${buildType.capitalized()}AndroidTest")) + + val android = project.extensions.getByName("android") as com.android.build.gradle.BaseExtension + val applicationId = android.defaultConfig.applicationId + val debugSuffix = android.buildTypes.getByName(buildType).applicationIdSuffix + val pkg = applicationId + debugSuffix + + adbPath.set(android.sdkDirectory.resolve("platform-tools/adb").absolutePath) + appPackage.set(pkg) + testPackage.set("$pkg.test") + testRunner.set(android.defaultConfig.testInstrumentationRunner) + + val testClassName = "ru.erdenian.studentassistant.AutomatedScreenshotTest" + testClass.set(testClassName) + + // Валидация существования файла теста на этапе конфигурации Gradle. + val relativeTestPath = "src/androidTest/kotlin/" + testClassName.replace('.', '/') + ".kt" + val testFile = layout.projectDirectory.file(relativeTestPath) + if (!testFile.asFile.exists()) { + throw GradleException("Test source file not found for class $testClassName. Expected at: $relativeTestPath") + } + + // Отслеживаем всю папку src, НО исключаем папку с ресурсами Play Store (куда мы пишем скриншоты), + // чтобы избежать циклического перезапуска задачи. + sources.from(layout.projectDirectory.dir("src")) + sources.exclude("main/play/**") + + tempDir.set(layout.buildDirectory.dir("screenshots_tmp")) + outputDir.set(layout.projectDirectory.dir("src/main/play/listings")) +} + +// endregion diff --git a/app/src/androidTest/kotlin/ru/erdenian/studentassistant/AutomatedScreenshotTest.kt b/app/src/androidTest/kotlin/ru/erdenian/studentassistant/AutomatedScreenshotTest.kt new file mode 100644 index 000000000..039ed1de0 --- /dev/null +++ b/app/src/androidTest/kotlin/ru/erdenian/studentassistant/AutomatedScreenshotTest.kt @@ -0,0 +1,768 @@ +package ru.erdenian.studentassistant + +import android.content.Context +import android.graphics.Bitmap +import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeLeft +import androidx.compose.ui.test.swipeRight +import androidx.core.os.LocaleListCompat +import androidx.test.platform.app.InstrumentationRegistry +import java.io.File +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.LocalTime +import java.time.temporal.ChronoUnit +import kotlin.math.abs +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import org.junit.After +import org.junit.Rule +import org.junit.Test +import ru.erdenian.studentassistant.di.MainComponentHolder +import ru.erdenian.studentassistant.repository.api.RepositoryApi +import ru.erdenian.studentassistant.strings.RS + +internal class AutomatedScreenshotTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + private val baseDate = LocalDate.now() + + private val locales = listOf( + "en" to "en-US", + "ru" to "ru-RU", + "fr" to "fr-FR", + "it" to "it-IT", + "de" to "de-DE", + "es" to "es-ES", + "be" to "be", + "uk" to "uk", + "kk" to "kk", + ) + + private data class LessonData( + val subject: String, + val type: String, + val teacher: String, + val room: String, + val start: LocalTime, + val end: LocalTime, + ) + + private data class HomeworkData( + val subject: String, + val description: String, + ) + + private data class LocalizedData( + val semesterName: String, + val lessons: List, + val homeworks: List, + ) + + private val localizedData = mapOf( + "en" to LocalizedData( + semesterName = "Fall Semester", + lessons = listOf( + LessonData( + subject = "Introduction to CS", + type = "Lecture", + teacher = "Prof. John Smith", + room = "Hall A", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Physics: Mechanics", + type = "Lecture", + teacher = "Dr. Emily White", + room = "Room 304", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Linear Algebra", + type = "Lecture", + teacher = "Dr. Alan Turing", + room = "Room 101", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Calculus I", + type = "Recitation", + teacher = "Jane Doe, MSc", + room = "Room 205", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Calculus I", + description = "Complete Problem Set #4 (Derivatives). Submit via portal.", + ), + HomeworkData( + subject = "Physics: Mechanics", + description = "Lab Report 2: Newton's Laws. Include error analysis.", + ), + HomeworkData( + subject = "Introduction to CS", + description = "Project: Implement a Binary Search Tree in Java.", + ), + HomeworkData( + subject = "Linear Algebra", + description = "Read Chapter 5. Solve exercises 5.1 - 5.10 (odd numbers).", + ), + HomeworkData( + subject = "Introduction to CS", + description = "Prepare for the midterm exam (topics: Loops, Arrays, OOP).", + ), + ), + ), + "ru" to LocalizedData( + semesterName = "Семестр 1", + lessons = listOf( + LessonData( + subject = "Основы программирования", + type = "Лекция", + teacher = "Гайдук Игорь Олегович", + room = "1201 м", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Физика", + type = "Лекция", + teacher = "Трифонов Алексей Юрьевич", + room = "1202 м", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Линейная алгебра", + type = "Лекция", + teacher = "Кожухов Игорь Борисович", + room = "1204 м", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Математический анализ", + type = "Практика", + teacher = "Шевченко Александр Игоревич", + room = "3244", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Математический анализ", + description = "Типовой расчет №2: Пределы и производные. Вариант 12.", + ), + HomeworkData( + subject = "Физика", + description = "Оформить отчет по лабораторной работе (Термодинамика) + графики.", + ), + HomeworkData( + subject = "Основы программирования", + description = "Курсовая: Разработать ER-диаграмму БД для библиотеки.", + ), + HomeworkData( + subject = "Линейная алгебра", + description = "Подготовиться к коллоквиуму по теме «Матрицы и определители».", + ), + HomeworkData( + subject = "Основы программирования", + description = "Реализовать алгоритм быстрой сортировки (QuickSort).", + ), + ), + ), + "fr" to LocalizedData( + semesterName = "Semestre d'automne", + lessons = listOf( + LessonData( + subject = "Introduction à la programmation", + type = "Cours magistral", + teacher = "Pr. Michel Dupont", + room = "Amphi B", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Physique : Mécanique", + type = "Cours magistral", + teacher = "Dr. Sophie Martin", + room = "Salle 102", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Algèbre linéaire", + type = "Cours magistral", + teacher = "Pr. Jean Renard", + room = "Amphi A", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Analyse mathématique", + type = "Travaux dirigés", + teacher = "Mme Claire Dubois", + room = "Salle 204", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Analyse mathématique", + description = "Exercices 1 à 5 sur les suites numériques (Fiche TD 3).", + ), + HomeworkData( + subject = "Physique : Mécanique", + description = "Rédiger le compte-rendu du TP n°2 (Lois de Newton).", + ), + HomeworkData( + subject = "Introduction à la programmation", + description = "Projet : Créer une base de données SQL simple.", + ), + HomeworkData( + subject = "Algèbre linéaire", + description = "Réviser pour le partiel : Espaces vectoriels.", + ), + HomeworkData( + subject = "Introduction à la programmation", + description = "Implémenter le tri à bulles en Java.", + ), + ), + ), + "it" to LocalizedData( + semesterName = "Primo Semestre", + lessons = listOf( + LessonData( + subject = "Fondamenti di Informatica", + type = "Lezione", + teacher = "Prof. Mario Rossi", + room = "Aula Magna", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Fisica Generale I", + type = "Lezione", + teacher = "Prof.ssa Anna Bianchi", + room = "Aula 3", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Algebra Lineare", + type = "Lezione", + teacher = "Prof. Giuseppe Verdi", + room = "Aula 1", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Analisi Matematica I", + type = "Esercitazione", + teacher = "Dott. Laura Esposito", + room = "Aula 4B", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Analisi Matematica I", + description = "Svolgere gli esercizi sulle derivate (Capitolo 4).", + ), + HomeworkData( + subject = "Fisica Generale I", + description = "Relazione di laboratorio: Esperienza sul pendolo.", + ), + HomeworkData( + subject = "Fondamenti di Informatica", + description = "Progetto database: schema E-R per una biblioteca.", + ), + HomeworkData( + subject = "Algebra Lineare", + description = "Risolvere il sistema di equazioni lineari (Metodo di Gauss).", + ), + HomeworkData( + subject = "Fondamenti di Informatica", + description = "Scrivere un programma per ordinare un array.", + ), + ), + ), + "de" to LocalizedData( + semesterName = "Wintersemester", + lessons = listOf( + LessonData( + subject = "Einführung in die Informatik", + type = "Vorlesung", + teacher = "Prof. Dr. Müller", + room = "Audimax", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Physik I: Mechanik", + type = "Vorlesung", + teacher = "Prof. Dr. Schmidt", + room = "HS 2", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Lineare Algebra", + type = "Vorlesung", + teacher = "Prof. Dr. Weber", + room = "HS 1", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Analysis I", + type = "Übung", + teacher = "Dr. Wagner", + room = "Raum 304", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Analysis I", + description = "Übungsblatt 5: Konvergenz von Folgen und Reihen.", + ), + HomeworkData( + subject = "Physik I: Mechanik", + description = "Versuchsprotokoll abgeben: Thermodynamik.", + ), + HomeworkData( + subject = "Einführung in die Informatik", + description = "Datenbankschema für eine Bibliothek entwerfen.", + ), + HomeworkData( + subject = "Lineare Algebra", + description = "Lösen von Gleichungssystemen (Gauß-Verfahren).", + ), + HomeworkData( + subject = "Einführung in die Informatik", + description = "Implementierung des Quicksort-Algorithmus.", + ), + ), + ), + "es" to LocalizedData( + semesterName = "Primer Semestre", + lessons = listOf( + LessonData( + subject = "Fundamentos de Programación", + type = "Clase teórica", + teacher = "Prof. García", + room = "Aula 101", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Física: Mecánica", + type = "Clase teórica", + teacher = "Dra. Rodríguez", + room = "Lab 3", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Álgebra Lineal", + type = "Clase teórica", + teacher = "Prof. Martínez", + room = "Aula 205", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Cálculo I", + type = "Práctica", + teacher = "Lic. López", + room = "Aula 10", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Cálculo I", + description = "Resolver problemas de derivadas (Ejercicios 1-10).", + ), + HomeworkData( + subject = "Física: Mecánica", + description = "Entregar informe de laboratorio (Termodinámica).", + ), + HomeworkData( + subject = "Fundamentos de Programación", + description = "Diseñar el esquema de base de datos para una biblioteca.", + ), + HomeworkData( + subject = "Álgebra Lineal", + description = "Resolver sistema de ecuaciones (Método de Gauss).", + ), + HomeworkData( + subject = "Fundamentos de Programación", + description = "Implementar algoritmo de ordenamiento rápido (QuickSort).", + ), + ), + ), + "be" to LocalizedData( + semesterName = "Семестр 1", + lessons = listOf( + LessonData( + subject = "Асновы праграмавання", + type = "Лекцыя", + teacher = "Гайдук Ігар Алегавіч", + room = "1201 м", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Фізіка", + type = "Лекцыя", + teacher = "Трыфанаў Аляксей Юр'евіч", + room = "1202 м", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Лінейная алгебра", + type = "Лекцыя", + teacher = "Кажухоў Ігар Барысавіч", + room = "1204 м", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Матэматычны аналіз", + type = "Практыка", + teacher = "Шаўчэнка Аляксандр Ігаравіч", + room = "3244", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Матэматычны аналіз", + description = "Індывідуальнае заданне №1: Граніцы функцый.", + ), + HomeworkData( + subject = "Фізіка", + description = "Падрыхтаваць справаздачу па лабараторнай працы (Тэрмадынаміка).", + ), + HomeworkData( + subject = "Асновы праграмавання", + description = "Распрацаваць схему базы дадзеных для бібліятэкі.", + ), + HomeworkData( + subject = "Лінейная алгебра", + description = "Рашыць сістэму лінейных раўнанняў метадам Гаўса.", + ), + HomeworkData( + subject = "Асновы праграмавання", + description = "Рэалізаваць алгарытм хуткай сарціроўкі.", + ), + ), + ), + "uk" to LocalizedData( + semesterName = "Семестр 1", + lessons = listOf( + LessonData( + subject = "Основи програмування", + type = "Лекція", + teacher = "Гайдук Ігор Олегович", + room = "1201 м", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Фізика", + type = "Лекція", + teacher = "Трифонов Олексій Юрійович", + room = "1202 м", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Лінійна алгебра", + type = "Лекція", + teacher = "Кожухов Ігор Борисович", + room = "1204 м", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Математичний аналіз", + type = "Практика", + teacher = "Шевченко Олександр Ігорович", + room = "3244", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Математичний аналіз", + description = "Розрахункова робота: Обчислити границі та похідні.", + ), + HomeworkData( + subject = "Фізика", + description = "Підготувати звіт з лабораторної роботи (Термодинаміка).", + ), + HomeworkData( + subject = "Основи програмування", + description = "Розробити схему бази даних для бібліотеки.", + ), + HomeworkData( + subject = "Лінійна алгебра", + description = "Розв'язати систему лінійних рівнянь методом Гауса.", + ), + HomeworkData( + subject = "Основи програмування", + description = "Реалізувати алгоритм швидкого сортування (QuickSort).", + ), + ), + ), + "kk" to LocalizedData( + semesterName = "1-семестр", + lessons = listOf( + LessonData( + subject = "Бағдарламалау негіздері", + type = "Дәріс", + teacher = "Ахметов Арман Әлиұлы", + room = "101 дәрісхана", + start = LocalTime.of(9, 0), + end = LocalTime.of(10, 20), + ), + LessonData( + subject = "Физика", + type = "Дәріс", + teacher = "Омаров Болат Бақытұлы", + room = "202 зертхана", + start = LocalTime.of(10, 30), + end = LocalTime.of(11, 50), + ), + LessonData( + subject = "Сызықтық алгебра", + type = "Дәріс", + teacher = "Сүлейменов Серік Саматұлы", + room = "305 дәрісхана", + start = LocalTime.of(12, 30), + end = LocalTime.of(13, 50), + ), + LessonData( + subject = "Математикалық талдау", + type = "Тәжірибелік сабақ", + teacher = "Ысқақова Гүлнар Ғабитқызы", + room = "304 аудитория", + start = LocalTime.of(14, 0), + end = LocalTime.of(15, 20), + ), + ), + homeworks = listOf( + HomeworkData( + subject = "Математикалық талдау", + description = "Туындыларды есептеу, 1-10 есептерді шығару.", + ), + HomeworkData( + subject = "Физика", + description = "Зертханалық жұмыс бойынша есеп беру (Термодинаміка).", + ), + HomeworkData( + subject = "Бағдарламалау негіздері", + description = "Кітапхана үшін деректер қорының схемасын құру.", + ), + HomeworkData( + subject = "Сызықтық алгебра", + description = "Сызықтық теңдеулер жүйесін Гаусс әдісімен шешу.", + ), + HomeworkData( + subject = "Бағдарламалау негіздері", + description = "Жылдам сұрыптау (QuickSort) алгоритмін жүзеге асыру.", + ), + ), + ), + ) + + @After + fun tearDown() { + // Сбрасываем локаль на системную после каждого теста (в том числе при падении) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList()) + } + } + + @Test + fun generateScreenshots() { + val args = InstrumentationRegistry.getArguments() + val isScreenshotMode = args.getString("is_screenshot_mode") == "true" + val repositoryApi = MainComponentHolder.instance.repositoryApi + + // Если режим скриншотов - проходим по всем языкам. + // Если обычный тест, то используем только первый язык + val iterations = if (isScreenshotMode) locales else locales.take(1) + for ((langCode, folderName) in iterations) { + // 1. Устанавливаем язык + setLocale(langCode) + + // 2. Заполняем БД + runBlocking { + withTimeout(5_000L) { + clearDatabase(repositoryApi) + populateDatabase(repositoryApi, langCode, baseDate) + } + } + + // 3. Ждем инициализации данных + runBlocking { + withTimeout(5000L) { + // Ждем, пока выберется именно то расписание, которое мы создали (по имени) + // Это защитит от использования старого ID + val targetName = localizedData[langCode]!!.semesterName + repositoryApi.selectedSemesterRepository.selectedFlow + .filterNotNull() + .filter { it.name == targetName } + .first() + } + } + + // 4. На первой итерации скроллим до понедельника, если сегодня не понедельник + // Делаем это только на первой итерации, так как выбранная дата сохраняется при смене локали + if (langCode == iterations.first().first) { + val monday = baseDate.with(DayOfWeek.MONDAY) + val daysDiff = ChronoUnit.DAYS.between(LocalDate.now(), monday).toInt() + + if (daysDiff != 0) { + val swipes = abs(daysDiff) + // Если разница положительная (пн в будущем), свайпаем влево + // (контент движется влево, показывая правый). + // Если отрицательная (пн в прошлом), свайпаем вправо. + val isFuture = daysDiff > 0 + repeat(swipes) { + composeTestRule.onRoot().performTouchInput { if (isFuture) swipeLeft() else swipeRight() } + } + } + } + + val targetContext = InstrumentationRegistry.getInstrumentation().targetContext + val currentData = localizedData.getValue(langCode) + + // 1. Расписание + if (isScreenshotMode) takeScreenshot(targetContext, folderName, "1") + + // 2. Задания + val homeworksTitle = targetContext.getString(RS.h_title) + composeTestRule.onNodeWithText(homeworksTitle).performClick() + if (isScreenshotMode) takeScreenshot(targetContext, folderName, "2") + + // 3. Детали занятия + val scheduleTitle = targetContext.getString(RS.s_title) + composeTestRule.onNodeWithText(scheduleTitle).performClick() + composeTestRule.onNodeWithText(currentData.lessons.last().room).performClick() + if (isScreenshotMode) takeScreenshot(targetContext, folderName, "3") + + // 4. Редактор расписания + composeTestRule.onNodeWithContentDescription(targetContext.getString(RS.u_back)).performClick() + composeTestRule + .onNodeWithContentDescription(targetContext.getString(RS.taba_more_options)) + .performClick() + composeTestRule.onNodeWithText(targetContext.getString(RS.s_edit)).performClick() + if (isScreenshotMode) takeScreenshot(targetContext, folderName, "4") + + // Возвращаемся на главный экран + composeTestRule.onNodeWithContentDescription(targetContext.getString(RS.u_back)).performClick() + } + } + + private suspend fun clearDatabase(api: RepositoryApi) = withContext(Dispatchers.IO) { + val semesters = api.semesterRepository.allFlow.first() + semesters.forEach { api.semesterRepository.delete(it.id) } + + // Ждем, пока репозиторий сбросит выбор расписания в null. + // Это гарантирует, что мы не подхватим старый ID в следующей итерации. + api.selectedSemesterRepository.selectedFlow.filter { it == null }.first() + } + + private suspend fun populateDatabase(api: RepositoryApi, langCode: String, baseDate: LocalDate) { + val data = localizedData.getValue(langCode) + + api.semesterRepository.insert( + name = data.semesterName, + firstDay = LocalDate.of(baseDate.year - 1, 1, 1), + lastDay = LocalDate.of(baseDate.year + 1, 12, 31), + ) + + // Ждем выбора именно НАШЕГО нового расписания + val semester = api.selectedSemesterRepository.selectedFlow + .filterNotNull() + .filter { it.name == data.semesterName } + .first() + val semesterId = semester.id + + data.lessons.forEach { lesson -> + api.lessonRepository.insert( + subjectName = lesson.subject, + type = lesson.type, + teachers = setOf(lesson.teacher), + classrooms = setOf(lesson.room), + startTime = lesson.start, + endTime = lesson.end, + semesterId = semesterId, + dayOfWeek = DayOfWeek.MONDAY, + weeks = listOf(true), + ) + } + + val nextMonday = baseDate.with(DayOfWeek.MONDAY).plusWeeks(1) + data.homeworks.forEachIndexed { index, homework -> + val deadline = nextMonday.plusDays(index.toLong()) + api.homeworkRepository.insert( + subjectName = homework.subject, + description = homework.description, + deadline = deadline, + semesterId = semesterId, + ) + } + } + + private fun setLocale(language: String) { + // Устанавливаем язык через AppCompatDelegate в UI-потоке + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val localeList = LocaleListCompat.forLanguageTags(language) + AppCompatDelegate.setApplicationLocales(localeList) + } + } + + private fun takeScreenshot(context: Context, folderName: String, fileName: String) { + composeTestRule.waitForIdle() + val bitmap = InstrumentationRegistry.getInstrumentation().uiAutomation.takeScreenshot() + val storageDir = context.getExternalFilesDir(null) + val dir = File(storageDir, "screenshots/$folderName") + dir.mkdirs() + File(dir, "$fileName.png").outputStream().use { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 689677f60..7cf5d4020 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,23 @@ - + + + + + + + + + + + + - + Па днях тыдня Па датах - - Семінар + + Занятак + Урок Лекцыя + Семінар Лабараторная работа Практыка - Кансультацыя + Залік Экзамен + Кансультацыя + Курсавая работа Самастойная работа + Факультатыў diff --git a/core/strings/src/main/res/values-be/strings.xml b/core/strings/src/main/res/values-be/strings.xml index 23e8677a2..42b2ca241 100644 --- a/core/strings/src/main/res/values-be/strings.xml +++ b/core/strings/src/main/res/values-be/strings.xml @@ -32,15 +32,15 @@ Новы расклад Рэдагаванне раскладу Назва - Першы дзень - Апошні дзень + Дата пачатку + Дата заканчэння Захаваць Захаванне… Увядзіце назву - Гэта імя ўжо выкарыстоўваецца + Гэтая назва ўжо выкарыстоўваецца Няправільны дыяпазон дат Зрух тыдняў - Вы змянілі дату пачатку семестра. Гэта можа парушыць чаргаванне тыдняў (цотныя/няцотныя) у існуючых заняткаў. Працягнуць? + Вы змянілі дату пачатку раскладу. Гэта можа парушыць чаргаванне тыдняў (цотныя/няцотныя) для існуючых заняткаў. Працягнуць? Так, працягнуць Адмена diff --git a/core/strings/src/main/res/values-cv/arrays.xml b/core/strings/src/main/res/values-cv/arrays.xml index a2bbc6de5..f45b24445 100644 --- a/core/strings/src/main/res/values-cv/arrays.xml +++ b/core/strings/src/main/res/values-cv/arrays.xml @@ -1,19 +1,24 @@ - + Эрне кунӗсем тӑрӑх Кунсем тӑрӑх - - Семинар + + Заняти + Урок Лекци + Семинар Лаборатори ӗҫӗ Практика - Консультаци + Зачет Экзамен + Консультаци + Курс ӗҫӗ Хам тӗллӗн ӗҫлени + Факультатив diff --git a/core/strings/src/main/res/values-cv/strings.xml b/core/strings/src/main/res/values-cv/strings.xml index 9902ce744..81704ec62 100644 --- a/core/strings/src/main/res/values-cv/strings.xml +++ b/core/strings/src/main/res/values-cv/strings.xml @@ -12,14 +12,14 @@ Килти ӗҫ ҫук - Урок йӗрки + Расписани Календарь - Йӗрке хуш - Йӗркене тӳрлет + Расписани хуш + Расписанине тӳрлет Кунта пушӑ. Университет е шкул расписанине хушӑр. - Урок ҫинчен + Заняти ҫинчен Тӳрлет Килти ӗҫ хуш Кӑларса пӑрах @@ -32,36 +32,36 @@ Ҫӗнӗ расписани Расписанине тӳрлетни Ят - Пӗрремӗш кун - Юлашки кун + Пуҫламӑш кун + Вӗҫленмелли кун Сыхла Сыхлани… Ятне ҫырӑр Ку ят йышӑннӑ ӗнтӗ Кунсем тӗрӗс мар - Эрне сӳрӗлсе кайни - Эсир семестр пуҫламӑшне улӑштартӑр. Ку уроксем эрне тӑрӑх (мӑшӑрлӑ/мӑшӑрсӑр) тӗрӗс мар тухма пултарать. Малалла? + Эрне йӗрки улшӑнни + Эсир расписани пуҫламӑшне улӑштартӑр. Ку занятисем эрне тӑрӑх (мӑшӑрлӑ/мӑшӑрсӑр) тӗрӗс мар тухма пултарать. Малалла? Малалла Пӑрахӑҫла - Урок редакторӗ + Заняти редакторӗ Параметрсем - Урок хуш + Заняти хуш Расписанине кӑларса пӑрах - Расписанипе урокcене пӗтӗмпех кӑларса пӑрахмалла-и? + Расписанипе занятисене пӗтӗмпех кӑларса пӑрахмалла-и? Кӑларса пӑрах Пӑрахӑҫла Расписанине кӑларса пӑрахни… Копиле Кӑларса пӑрах - Урока кӑларса пӑрахни… + Занятие кӑларса пӑрахни… - Ҫӗнӗ урок - Урока тӳрлетни + Ҫӗнӗ заняти + Занятие тӳрлетни Предмет - Урок тӗсӗ + Заняти тӗсӗ Вӗрентекенсем Пӳлӗмсем Пуҫламӑш @@ -73,14 +73,14 @@ Предмет ятне ҫырӑр Пуҫламӑшӗ вӗҫӗнчен ирех пулмалла Хӑш кунсенче иртнине палӑртман - Ытти уроксене тӗрӗслени… + Ытти занятисене тӗрӗслени… Ушкӑнпа улӑштарни - Эсир предмет ятне улӑштартӑр. Ытти уроксенче те улӑштармалла-и? + Эсир предмет ятне улӑштартӑр. Ытти занятисенче те улӑштармалла-и? Улӑштар Кирлӗ мар Пӑрахӑҫла - Урока кӑларса пӑрах - Ку урока кӑларса пӑрахмалла-и? + Занятие кӑларса пӑрах + Ку занятие кӑларса пӑрахмалла-и? Кӑларса пӑрах Пӑрахӑҫла Кӑларса пӑрахни… @@ -118,7 +118,7 @@ Расписанире ку предмет ҫук. Килти ӗҫе сыхласа предмета списока хушмалла-и? Сыхла Пӑрахӑҫла - Сыхла тата урок ту + Сыхла тата заняти ту Кӑларса пӑрах Килти ӗҫе кӑларса пӑрахмалла-и? Кӑларса пӑрах @@ -131,11 +131,11 @@ Настройкӑсем - Урок пуҫламӑшӗ - Урок тӑсӑлӑвӗ - Тăхтав тӑсӑлӑвӗ + Заняти пуҫламӑшӗ + Заняти тӑсӑлӑвӗ + Тӑхтав тӑсӑлӑвӗ Анлӑрах эрне суйлавӗ - Урока тӑтӑшлӑхне суйламалли анлӑрах майсем + Занятие тӑтӑшлӑхне суйламалли анлӑрах майсем Юрать Пӑрахӑҫла diff --git a/core/strings/src/main/res/values-de/arrays.xml b/core/strings/src/main/res/values-de/arrays.xml index 8b7981159..94499bebe 100644 --- a/core/strings/src/main/res/values-de/arrays.xml +++ b/core/strings/src/main/res/values-de/arrays.xml @@ -1,19 +1,25 @@ - + Nach Wochentagen Nach Datum - - Seminar + + Kurs + Unterricht Vorlesung - Laborübung + Übung + Seminar Praktikum - Sprechstunde + Tutorium + Klausur Prüfung + Kolloquium + Sprechstunde Selbststudium + Wahlfach diff --git a/core/strings/src/main/res/values-de/strings.xml b/core/strings/src/main/res/values-de/strings.xml index fb7202bb8..a386e3122 100644 --- a/core/strings/src/main/res/values-de/strings.xml +++ b/core/strings/src/main/res/values-de/strings.xml @@ -16,7 +16,7 @@ Kalender Stundenplan hinzufügen Stundenplan bearbeiten - Hier ist es leer. Fügen Sie einen Uni- oder Schulstundenplan hinzu. + Noch kein Stundenplan vorhanden. Fügen Sie einen Uni- oder Schulstundenplan hinzu. Infos zur Stunde @@ -32,21 +32,21 @@ Neuer Stundenplan Stundenplan bearbeiten Name - Erster Tag - Letzter Tag + Startdatum + Enddatum Speichern Wird gespeichert… Name eingeben Dieser Name wird bereits verwendet Ungültiger Datumsbereich Wochenverschiebung - Sie haben das Startdatum des Semesters geändert. Dies kann den Wochenrhythmus (gerade/ungerade) für bestehende Stunden stören. Fortfahren? + Sie haben das Startdatum des Stundenplans geändert. Dies kann den Wochenrhythmus (gerade/ungerade) für bestehende Stunden stören. Fortfahren? Ja, fortfahren Abbrechen Stundeneditor - Parameter + Stundenplan-Einstellungen Stunde hinzufügen Stundenplan löschen Stundenplan und alle Stunden löschen? @@ -62,7 +62,7 @@ Stunde bearbeiten Fach Art der Stunde - Lehrer + Lehrende Räume Beginn Ende @@ -98,7 +98,7 @@ Hausaufgaben Neue Hausaufgabe - Hier ist es leer. Fügen Sie einen Stundenplan hinzu, um Hausaufgaben zu speichern. + Noch keine Hausaufgaben. Fügen Sie einen Stundenplan hinzu, um Hausaufgaben zu speichern. Löschen Diese Hausaufgabe löschen? Löschen @@ -120,7 +120,7 @@ Abbrechen Speichern und Stunde erstellen Löschen - Hausaufgabe löschen? + Diese Hausaufgabe löschen? Löschen Abbrechen Wird gelöscht… diff --git a/core/strings/src/main/res/values-es/arrays.xml b/core/strings/src/main/res/values-es/arrays.xml index 39909d8f2..bd6135df3 100644 --- a/core/strings/src/main/res/values-es/arrays.xml +++ b/core/strings/src/main/res/values-es/arrays.xml @@ -1,29 +1,33 @@ - + Por días de la semana Por fechas - - Seminario - Conferencia - Trabajo de laboratorio + + Clase + Clase teórica Práctica - Consulta + Laboratorio + Seminario + Taller + Tutoría + Parcial Examen Trabajo independiente + Optativa Cada semana Semanas impares Semanas pares - Cada 1ª semana de 4 - Cada 2ª semana de 4 - Cada 3ª semana de 4 - Cada 4ª semana de 4 + Semana 1 de 4 + Semana 2 de 4 + Semana 3 de 4 + Semana 4 de 4 Personalizado diff --git a/core/strings/src/main/res/values-es/strings.xml b/core/strings/src/main/res/values-es/strings.xml index c9e6c8626..e0ce9fca3 100644 --- a/core/strings/src/main/res/values-es/strings.xml +++ b/core/strings/src/main/res/values-es/strings.xml @@ -16,7 +16,7 @@ Calendario Añadir horario Editar horario - Está vacío aquí. Añade un horario universitario o escolar. + No hay horarios aún. Añade un horario universitario o escolar. Info de la clase @@ -32,21 +32,21 @@ Nuevo horario Editar horario Nombre - Primer día - Último día + Fecha de inicio + Fecha de fin Guardar Guardando… Introduce un nombre Este nombre ya está en uso Rango de fechas inválido Cambio de semanas - Has cambiado la fecha de inicio del semestre. Esto puede alterar la alternancia de semanas (pares/impares) para las clases existentes. ¿Continuar? + Has cambiado la fecha de inicio del horario. Esto puede alterar la alternancia de semanas (pares/impares) para las clases existentes. ¿Continuar? Sí, continuar Cancelar Editor de clases - Parámetros + Parámetros del horario Añadir clase Eliminar horario ¿Eliminar el horario y todas las clases? @@ -98,7 +98,7 @@ Tareas Nueva tarea - Está vacío aquí. Añade un horario para guardar tareas. + No hay tareas. Añade un horario para guardar tareas. Eliminar ¿Eliminar esta tarea? Eliminar @@ -120,7 +120,7 @@ Cancelar Guardar y crear clase Eliminar - ¿Eliminar tarea? + ¿Eliminar esta tarea? Eliminar Cancelar Eliminando… diff --git a/core/strings/src/main/res/values-fr/arrays.xml b/core/strings/src/main/res/values-fr/arrays.xml index 818064b3a..6cb8be7d8 100644 --- a/core/strings/src/main/res/values-fr/arrays.xml +++ b/core/strings/src/main/res/values-fr/arrays.xml @@ -1,29 +1,34 @@ - + Par jours de la semaine Par dates - - Séminaire + + Cours Cours magistral - Travaux pratiques Travaux dirigés - Consultation + Travaux pratiques + Séminaire + Partiel Examen + Soutenance + Conférence + Consultation Travail personnel + Option Chaque semaine Semaines impaires Semaines paires - Toutes les 1re semaines sur 4 - Toutes les 2e semaines sur 4 - Toutes les 3e semaines sur 4 - Toutes les 4e semaines sur 4 + Semaine 1 sur 4 + Semaine 2 sur 4 + Semaine 3 sur 4 + Semaine 4 sur 4 Personnalisé diff --git a/core/strings/src/main/res/values-fr/strings.xml b/core/strings/src/main/res/values-fr/strings.xml index 3f808e0f3..05c978388 100644 --- a/core/strings/src/main/res/values-fr/strings.xml +++ b/core/strings/src/main/res/values-fr/strings.xml @@ -16,7 +16,7 @@ Calendrier Ajouter un emploi du temps Modifier l\'emploi du temps - C\'est vide ici. Ajoutez un emploi du temps universitaire ou scolaire. + Aucun emploi du temps. Ajoutez un planning universitaire ou scolaire. Infos sur le cours @@ -32,21 +32,21 @@ Nouvel emploi du temps Modifier l\'emploi du temps Nom - Premier jour - Dernier jour + Date de début + Date de fin Enregistrer Enregistrement… Entrez un nom Ce nom est déjà utilisé Plage de dates invalide Décalage des semaines - Vous avez modifié la date de début du semestre. Cela peut perturber l\'alternance des semaines (paires/impaires) pour les cours existants. Continuer ? + Vous avez modifié la date de début de l\'emploi du temps. Cela peut perturber l\'alternance des semaines (paires/impaires) pour les cours existants. Continuer ? Oui, continuer Annuler - Éditeur de cours - Paramètres + Éditeur d\'emploi du temps + Paramètres de l\'emploi du temps Ajouter un cours Supprimer l\'emploi du temps Supprimer l\'emploi du temps et tous les cours ? @@ -67,12 +67,12 @@ Début Fin Jour de la semaine - Répétition + Périodicité Enregistrer Enregistrement… Entrez le nom de la matière L\'heure de début doit être avant l\'heure de fin - Aucun jour de répétition sélectionné + Aucun jour sélectionné Vérification des autres cours… Renommage groupé Vous avez renommé la matière. Mettre à jour pour les autres cours aussi ? @@ -98,7 +98,7 @@ Devoirs Nouveau devoir - C\'est vide ici. Ajoutez un emploi du temps pour enregistrer les devoirs. + Aucun devoir. Ajoutez un emploi du temps pour enregistrer les devoirs. Supprimer Supprimer ce devoir ? Supprimer @@ -120,7 +120,7 @@ Annuler Enregistrer et créer le cours Supprimer - Supprimer le devoir ? + Supprimer ce devoir ? Supprimer Annuler Suppression… @@ -153,7 +153,7 @@ Jusqu\'au %1$s - Répétition + Périodicité Longueur du cycle : %1$d Ajouter une semaine Supprimer une semaine diff --git a/core/strings/src/main/res/values-it/arrays.xml b/core/strings/src/main/res/values-it/arrays.xml index 7f7e572f2..8442e82c7 100644 --- a/core/strings/src/main/res/values-it/arrays.xml +++ b/core/strings/src/main/res/values-it/arrays.xml @@ -1,19 +1,22 @@ - + Per giorni della settimana Per date - - Seminario + Lezione - Laboratorio Esercitazione + Laboratorio + Seminario Ricevimento Esame + Parziale + Tirocinio Lavoro autonomo + Corso a scelta diff --git a/core/strings/src/main/res/values-it/strings.xml b/core/strings/src/main/res/values-it/strings.xml index d256e9116..94132e1f1 100644 --- a/core/strings/src/main/res/values-it/strings.xml +++ b/core/strings/src/main/res/values-it/strings.xml @@ -16,7 +16,7 @@ Calendario Aggiungi orario Modifica orario - È vuoto qui. Aggiungi un orario universitario o scolastico. + Nessun orario presente. Aggiungi un orario universitario o scolastico. Info lezione @@ -32,21 +32,21 @@ Nuovo orario Modifica orario Nome - Primo giorno - Ultimo giorno + Data di inizio + Data di fine Salva Salvataggio… - Inserisci nome + Inserisci un nome Questo nome è già in uso Intervallo di date non valido Spostamento settimane - Hai cambiato la data di inizio del semestre. Questo potrebbe alterare l\'alternanza delle settimane (pari/dispari) per le lezioni esistenti. Continuare? + Hai cambiato la data di inizio dell\'orario. Questo potrebbe alterare l\'alternanza delle settimane (pari/dispari) per le lezioni esistenti. Continuare? Sì, continua Annulla Editor lezioni - Parametri + Parametri orario Aggiungi lezione Elimina orario Eliminare l\'orario e tutte le lezioni? @@ -98,7 +98,7 @@ Compiti Nuovo compito - È vuoto qui. Aggiungi un orario per salvare i compiti. + Nessun orario presente. Aggiungine uno per salvare i compiti. Elimina Eliminare questo compito? Elimina @@ -120,7 +120,7 @@ Annulla Salva e crea lezione Elimina - Eliminare il compito? + Eliminare questo compito? Elimina Annulla Eliminazione… diff --git a/core/strings/src/main/res/values-kk/arrays.xml b/core/strings/src/main/res/values-kk/arrays.xml index 82b2781d9..bd8dcc52c 100644 --- a/core/strings/src/main/res/values-kk/arrays.xml +++ b/core/strings/src/main/res/values-kk/arrays.xml @@ -1,19 +1,23 @@ - + Апта күндері бойынша Күндер бойынша - - Семинар + + Сабақ Дәріс + Семинар Зертханалық жұмыс - Практика - Кеңес беру + Тәжірибелік сабақ + Сынақ Емтихан + Кеңес беру + Курстық жұмыс Өздік жұмыс + Факультатив diff --git a/core/strings/src/main/res/values-kk/strings.xml b/core/strings/src/main/res/values-kk/strings.xml index 2f073ee18..20ef692cc 100644 --- a/core/strings/src/main/res/values-kk/strings.xml +++ b/core/strings/src/main/res/values-kk/strings.xml @@ -32,15 +32,15 @@ Жаңа кесте Кестені өңдеу Атауы - Бірінші күн - Соңғы күн + Басталу күні + Аяқталу күні Сақтау Сақталуда… Атауын енгізіңіз Бұл атау қолданылуда Күндер дұрыс емес Апталардың ауысуы - Сіз семестрдің басталу күнін өзгерттіңіз. Бұл бар сабақтардың апталық кезегін (жұп/тақ) бұзуы мүмкін. Жалғастыру керек пе? + Сіз кестенің басталу күнін өзгерттіңіз. Бұл бар сабақтардың апталық кезегін (жұп/тақ) бұзуы мүмкін. Жалғастыру керек пе? Иә, жалғастыру Болдырмау diff --git a/core/strings/src/main/res/values-ru/arrays.xml b/core/strings/src/main/res/values-ru/arrays.xml index 2b8e2b638..6d06d62a6 100644 --- a/core/strings/src/main/res/values-ru/arrays.xml +++ b/core/strings/src/main/res/values-ru/arrays.xml @@ -1,30 +1,35 @@ - + По дням недели По датам - - Семинар + + Занятие + Урок Лекция + Семинар Лабораторная работа Практика - Консультация + Зачёт Экзамен + Консультация + Курсовая работа Самостоятельная работа + Факультатив Каждую неделю По нечётным По чётным - По первым числителям - По первым знаменателям - По вторым числителям - По вторым знаменателям + Каждую 1-ю неделю из 4 + Каждую 2-ю неделю из 4 + Каждую 3-ю неделю из 4 + Каждую 4-ю неделю из 4 Своё - \ No newline at end of file + diff --git a/core/strings/src/main/res/values-ru/strings.xml b/core/strings/src/main/res/values-ru/strings.xml index 1470354c9..7a4e1bdba 100644 --- a/core/strings/src/main/res/values-ru/strings.xml +++ b/core/strings/src/main/res/values-ru/strings.xml @@ -1,3 +1,4 @@ + Student Assistant @@ -31,15 +32,15 @@ Новое расписание Редактирование расписания Название - Первый день - Последний день + Дата начала + Дата окончания Сохранить Сохранение… Введите название - Это имя уже используется + Это название уже используется Некорректный диапазон дат Сдвиг недель - Вы изменили дату начала семестра. Это может нарушить чередование недель (чётные/нечётные) у существующих занятий. Продолжить? + Вы изменили дату начала расписания. Это может нарушить чередование недель (чётные/нечётные) для существующих занятий. Продолжить? Да, продолжить Отмена @@ -132,7 +133,7 @@ Настройки Начало занятий Длительность занятия - Длительность перемены + Длительность перерыва Расширенный выбор недель Более гибкий выбор недель для повторения занятия ОК diff --git a/core/strings/src/main/res/values-uk/arrays.xml b/core/strings/src/main/res/values-uk/arrays.xml index 9550dd180..e257890a3 100644 --- a/core/strings/src/main/res/values-uk/arrays.xml +++ b/core/strings/src/main/res/values-uk/arrays.xml @@ -1,19 +1,24 @@ - + По днях тижня По датах - - Семінар + + Заняття + Урок Лекція + Семінар Лабораторна робота Практика - Консультація + Залік Екзамен + Консультація + Курсова робота Самостійна робота + Факультатив diff --git a/core/strings/src/main/res/values-uk/strings.xml b/core/strings/src/main/res/values-uk/strings.xml index f7e589c66..1152888a6 100644 --- a/core/strings/src/main/res/values-uk/strings.xml +++ b/core/strings/src/main/res/values-uk/strings.xml @@ -32,15 +32,15 @@ Новий розклад Редагування розкладу Назва - Перший день - Останній день + Дата початку + Дата закінчення Зберегти Збереження… Введіть назву - Це ім\'я вже використовується + Ця назва вже використовується Некоректний діапазон дат Зсув тижнів - Ви змінили дату початку семестру. Це може порушити чергування тижнів (парні/непарні) в існуючих заняттях. Продовжити? + Ви змінили дату початку розкладу. Це може порушити чергування тижнів (парні/непарні) для існуючих занять. Продовжити? Так, продовжити Скасувати diff --git a/core/strings/src/main/res/values/arrays.xml b/core/strings/src/main/res/values/arrays.xml index 181f061e9..fd9020a7f 100644 --- a/core/strings/src/main/res/values/arrays.xml +++ b/core/strings/src/main/res/values/arrays.xml @@ -1,19 +1,25 @@ - + By days of week By dates - - Seminar + + Class + Lesson Lecture - Laboratory work - Practical work - Consultation + Seminar + Tutorial + Lab + Recitation + Workshop + Office Hours + Midterm + Final Exam Exam - Independent work + Elective diff --git a/core/strings/src/main/res/values/strings.xml b/core/strings/src/main/res/values/strings.xml index bc86d0f83..2658e8071 100644 --- a/core/strings/src/main/res/values/strings.xml +++ b/core/strings/src/main/res/values/strings.xml @@ -16,10 +16,10 @@ Calendar Add schedule Edit schedule - It\'s empty here. Add a university or school schedule. + No schedule yet. Add a university or school schedule. - Lesson information + Class information Edit Add homework Delete @@ -32,36 +32,36 @@ New schedule Edit schedule Name - First day - Last day + Start date + End date Save Saving… - Enter name + Enter a name This name is already in use Invalid date range Week shift - You changed the semester start date. This may disrupt the week alternation (even/odd) for existing lessons. Continue? + You changed the schedule start date. This may disrupt the week alternation (even/odd) for existing classes. Continue? Yes, continue Cancel Schedule editor Schedule parameters - Add lesson + Add class Delete schedule - Delete schedule and all lessons? + Delete schedule and all classes? Delete Cancel Deleting schedule… Copy Delete - Deleting lesson… + Deleting class… - New lesson - Edit lesson + New class + Edit class Subject - Lesson type + Class type Teachers Classrooms Start @@ -70,17 +70,17 @@ Repeat Save Saving… - Enter subject name + Enter a subject name Start time must be before end time No repeat days selected - Checking other lessons… + Checking other classes… Group rename - You renamed the subject. Update it for other lessons too? + You renamed the subject. Update it for other classes too? Yes No Cancel - Delete lesson - Delete this lesson? + Delete class + Delete this class? Delete Cancel Deleting… @@ -98,7 +98,7 @@ Homework New homework - It\'s empty here. Add a schedule to save homework. + No homework yet. Add a schedule to start tracking assignments. Delete Delete this homework? Delete @@ -112,15 +112,15 @@ Deadline Save Saving… - Enter subject - Enter description + Enter a subject + Enter a description New subject The subject is not in the schedule. Save homework and add subject to the list? Save Cancel - Save and create lesson + Save and create class Delete - Delete homework? + Delete this homework? Delete Cancel Deleting… @@ -131,11 +131,11 @@ Settings - Classes start time - Lesson duration + Class start time + Class duration Break duration Advanced week selector - More flexible week selection for lesson repetition + More flexible week selection for class repetition OK Cancel @@ -150,7 +150,7 @@ More options - Until %1$s + Due %1$s Repeat diff --git a/features/homework/src/main/kotlin/ru/erdenian/studentassistant/homework/homeworks/HomeworksViewModel.kt b/features/homework/src/main/kotlin/ru/erdenian/studentassistant/homework/homeworks/HomeworksViewModel.kt index 8c31436b3..67ad6ada7 100644 --- a/features/homework/src/main/kotlin/ru/erdenian/studentassistant/homework/homeworks/HomeworksViewModel.kt +++ b/features/homework/src/main/kotlin/ru/erdenian/studentassistant/homework/homeworks/HomeworksViewModel.kt @@ -44,9 +44,9 @@ internal class HomeworksViewModel @Inject constructor( .stateIn(viewModelScope, SharingStarted.Default, listOfNotNull(selectedSemester.value)) /** - * Выбирает семестр для отображения заданий. + * Выбирает расписание для отображения заданий. * - * @param semesterId идентификатор семестра. + * @param semesterId идентификатор расписания. */ fun selectSemester(semesterId: Long) { selectedSemesterRepository.selectSemester(semesterId) diff --git a/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/Fakes.kt b/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/Fakes.kt index de7f3b34e..d3d3f7563 100644 --- a/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/Fakes.kt +++ b/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/Fakes.kt @@ -103,7 +103,7 @@ internal class FakeHomeworkRepository : HomeworkRepository { internal class FakeLessonRepository : LessonRepository { val lessons = MutableStateFlow>(emptyList()) - // Ссылка на семестры нужна для вычисления номера недели + // Ссылка на расписания нужна для вычисления номера недели var semesters: List = emptyList() override suspend fun insert( diff --git a/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/lessoneditor/LessonEditorScreenTest.kt b/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/lessoneditor/LessonEditorScreenTest.kt index 51b61d85b..b5a85feee 100644 --- a/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/lessoneditor/LessonEditorScreenTest.kt +++ b/features/schedule/src/androidTest/kotlin/ru/erdenian/studentassistant/schedule/lessoneditor/LessonEditorScreenTest.kt @@ -170,8 +170,8 @@ internal class LessonEditorScreenTest { ) lessonRepository.lessons.value = listOf(lesson) - // Симулируем наличие домашки и то, что это последний урок такого типа - // (FakeLessonRepository.getCount вернет 1, так как урок в списке один) + // Симулируем наличие домашки и то, что это последнее занятие такого типа + // (FakeLessonRepository.getCount вернет 1, так как занятие в списке одно) homeworkRepository.hasHomeworksResult = true val navigator = mockk(relaxed = true) @@ -196,7 +196,7 @@ internal class LessonEditorScreenTest { @Test fun verifyRenameOthersDialog() { - // Два урока с одинаковым предметом + // Два занятия с одинаковым предметом val lesson1 = Lesson( subjectName = "Математика", type = "Лекция", @@ -240,7 +240,7 @@ internal class LessonEditorScreenTest { composeTestRule.waitForIdle() - // Проверяем, что оба урока переименованы (FakeRepo реализует renameSubject) + // Проверяем, что оба занятия переименованы (FakeRepo реализует renameSubject) val lessons = lessonRepository.lessons.value assertEquals("Алгебра", lessons.find { it.id == 10L }?.subjectName) assertEquals("Алгебра", lessons.find { it.id == 11L }?.subjectName) diff --git a/features/schedule/src/main/kotlin/ru/erdenian/studentassistant/schedule/scheduleeditor/ScheduleEditorViewModel.kt b/features/schedule/src/main/kotlin/ru/erdenian/studentassistant/schedule/scheduleeditor/ScheduleEditorViewModel.kt index 53592f873..450b75618 100644 --- a/features/schedule/src/main/kotlin/ru/erdenian/studentassistant/schedule/scheduleeditor/ScheduleEditorViewModel.kt +++ b/features/schedule/src/main/kotlin/ru/erdenian/studentassistant/schedule/scheduleeditor/ScheduleEditorViewModel.kt @@ -90,7 +90,7 @@ internal class ScheduleEditorViewModel @AssistedInject constructor( } /** - * Отправляет событие аналитики при нажатии на кнопку редактирования семестра. + * Отправляет событие аналитики при нажатии на кнопку редактирования расписания. */ fun logEditSemesterClicked() { analytics.logEvent("semester_edit_clicked") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec7b46c63..ac29d4636 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,6 +52,7 @@ test_mockk_android = { group = "io.mockk", name = "mockk-android", version.ref = test_androidx_junitKtx = { group = "androidx.test.ext", name = "junit-ktx", version = "1.3.0" } test_androidx_core = { group = "androidx.test", name = "core-ktx", version.ref = "test_androidx_common" } test_androidx_runner = { group = "androidx.test", name = "runner", version.ref = "test_androidx_common" } +test_androidx_espressoCore = { group = "androidx.test.espresso", name = "espresso-core", version = "3.7.0" } # Compose test_compose_junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } @@ -69,7 +70,7 @@ androidTools_desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk # endregion # region Firebase -firebase_bom = { group = "com.google.firebase", name = "firebase-bom", version = "33.16.0" } +firebase_bom = { group = "com.google.firebase", name = "firebase-bom", version = "34.7.0" } firebase_crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" } firebase_analytics = { group = "com.google.firebase", name = "firebase-analytics" } # endregion @@ -115,7 +116,8 @@ test_android = [ "test_androidx_junitKtx", "test_androidx_core", "test_androidx_runner", - "test_mockk_android" + "test_mockk_android", + "test_androidx_espressoCore" ] test_compose = [