From 3c2ca41526ffb12778b0b0ea377e79d37fdf5ba0 Mon Sep 17 00:00:00 2001 From: zuevval Date: Tue, 26 Jan 2021 20:03:35 +0300 Subject: [PATCH 01/58] #248 normalize line endings --- .gitattributes | 10 + .gitignore | 454 +++---- CONTRIBUTING.md | 114 +- README.md | 66 +- app/.gitignore | 2 +- app/app.iml | 514 ++++---- app/build.gradle | 244 ++-- app/proguard-rules.pro | 42 +- .../data/db/LearnBrailleDatabaseTest.kt | 366 +++--- app/src/main/AndroidManifest.xml | 52 +- .../learnbraille/LearnBrailleApplication.kt | 244 ++-- .../data/db/LearnBrailleDatabase.kt | 654 +++++----- .../learnbraille/data/entities/BrailleDots.kt | 156 +-- .../learnbraille/data/entities/Lessons.kt | 68 +- .../learnbraille/data/entities/Users.kt | 66 +- .../learnbraille/ui/screens/MainActivity.kt | 74 +- .../ui/screens/exit/ExitFragment.kt | 82 +- .../ui/screens/help/HelpFragment.kt | 66 +- .../ui/screens/menu/MenuFragment.kt | 324 ++--- .../ui/screens/practice/CardFragment.kt | 384 +++--- .../ui/screens/practice/CardViewModel.kt | 300 ++--- .../ui/screens/settings/SettingsFragment.kt | 22 +- .../learnbraille/ui/views/BrailleDotsView.kt | 464 +++---- .../drawable-v24/ic_launcher_foreground.xml | 68 +- .../res/drawable/action_menu_help_button.xml | 18 +- .../res/drawable/checked_round_checkbox.xml | 14 +- .../res/drawable/ic_launcher_background.xml | 340 ++--- app/src/main/res/drawable/round_checkbox.xml | 10 +- .../res/drawable/unchecked_round_checkbox.xml | 18 +- app/src/main/res/layout/activity_main.xml | 32 +- app/src/main/res/layout/braille_dots_view.xml | 206 +-- app/src/main/res/layout/fragment_card.xml | 216 ++-- app/src/main/res/layout/fragment_exit.xml | 70 +- app/src/main/res/layout/fragment_help.xml | 28 +- app/src/main/res/layout/fragment_menu.xml | 214 ++-- .../res/mipmap-anydpi-v26/ic_launcher.xml | 8 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 8 +- app/src/main/res/navigation/navigation.xml | 496 +++---- app/src/main/res/values-sw600dp/dimens.xml | 72 +- app/src/main/res/values-sw720dp/dimens.xml | 70 +- app/src/main/res/values/colors.xml | 20 +- .../res/values/ic_launcher_background.xml | 6 +- app/src/main/res/values/strings.xml | 1134 ++++++++--------- app/src/main/res/values/styles.xml | 184 +-- .../data/entities/BrailleDotsTest.kt | 30 +- .../learnbraille/utils/UtilsTest.kt | 74 +- build.gradle | 76 +- gradle.properties | 42 +- gradlew.bat | 168 +-- settings.gradle | 4 +- 50 files changed, 4202 insertions(+), 4192 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d43da689 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# icons are binary +*.png binary + +# code files and `gradlew` should always have LF line endings +*.kt text +*.xml text +gradlew text + +# other files are treated as text by default +* text=auto diff --git a/.gitignore b/.gitignore index 9544a0dc..b9f22659 100644 --- a/.gitignore +++ b/.gitignore @@ -1,227 +1,227 @@ -# auto-generated scripts -detekt -android-wait-for-emulator - -# Created by https://www.toptal.com/developers/gitignore/api/androidstudio,android,gradle -# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,android,gradle - -### Android ### -# Built application files -*.apk -*.aar -*.ap_ -*.aab - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ -out/ -# Uncomment the following line in case you need and you don't have the release build type files in your app -# release/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml - -# Keystore files -# Uncomment the following lines if you do not want to check your keystore files in. -#*.jks -#*.keystore - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild -.cxx/ - -# Google Services (e.g. APIs or Firebase) -# google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json - -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md - -# Version control -vcs.xml - -# lint -lint/intermediates/ -lint/generated/ -lint/outputs/ -lint/tmp/ -# lint/reports/ - -### Android Patch ### -gen-external-apklibs -output.json - -# Replacement of .externalNativeBuild directories introduced -# with Android Studio 3.5. - -### Gradle ### -.gradle - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Cache of project -.gradletasknamecache - -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties - -### Gradle Patch ### -**/build/ - -### AndroidStudio ### -# Covers files to be ignored for android development using Android Studio. - -# Built application files - -# Files for the ART/Dalvik VM - -# Java class files - -# Generated files - -# Gradle files - -# Signing files -.signing/ - -# Local configuration file (sdk path, etc) - -# Proguard folder generated by Eclipse - -# Log Files - -# Android Studio -/*/build/ -/*/local.properties -/*/out -/*/*/build -/*/*/production -*.ipr -*~ -*.swp - -# Android Patch - -# External native build folder generated in Android Studio 2.2 and later - -# NDK -obj/ - -# IntelliJ IDEA -*.iws -/out/ - -# User-specific configurations -.idea/caches/ -.idea/libraries/ -.idea/shelf/ -.idea/.name -.idea/compiler.xml -.idea/copyright/profiles_settings.xml -.idea/encodings.xml -.idea/misc.xml -.idea/scopes/scope_settings.xml -.idea/vcs.xml -.idea/jsLibraryMappings.xml -.idea/datasources.xml -.idea/dataSources.ids -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# OS-specific files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Legacy Eclipse project files -.classpath -.project -.cproject -.settings/ - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.ear - -# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) -hs_err_pid* - -## Plugin-specific files: - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Mongo Explorer plugin -.idea/mongoSettings.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -### AndroidStudio Patch ### - -!/gradle/wrapper/gradle-wrapper.jar - -# End of https://www.toptal.com/developers/gitignore/api/androidstudio,android,gradle +# auto-generated scripts +detekt +android-wait-for-emulator + +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio,android,gradle +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,android,gradle + +### Android ### +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +### Android Patch ### +gen-external-apklibs +output.json + +# Replacement of .externalNativeBuild directories introduced +# with Android Studio 3.5. + +### Gradle ### +.gradle + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +### Gradle Patch ### +**/build/ + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*~ +*.swp + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio,android,gradle diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8c8710a..8ea931cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,57 +1,57 @@ -# Contributing to Learn Braille - -## Build - -To work with the project you need `android-studio >= 3.3`, you can get it [here](https:://developer.android.com/studio). - -For building and running the app, your either need to download a virtual Android device image (this can be done from within the Android Studio) or connect a physical device (with developer options enabled and USB debugging turned on) via USB cable. - -## Git workflow - -- Write short and informative commit messages. Mention the issue you're working on (begins with `#`), e. g. `implement something(#0)`. [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/). -- Follow [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model/). - -## Coding style - -- Kotlin [coding conventions](https://kotlinlang.org/docs/reference/coding-conventions.html) + Java [google style guides](https://google.github.io/styleguide/javaguide.html) and [oracle](https://www.oracle.com/technetwork/java/codeconvtoc-136057.html). -- Also look around each time you do something new to see, how such a thing was formatted and implemented before. -- Set up Android Studio proper Kotlin code style `editor -> code style -> kotlin -> set from -> predefined -> Kotlin style guide`. -- Apply autoformatting to edited files each time before commit. - -## Adding content - -There is a handy DSL that allows writing content in a typesafe way. - -- All app content should be placed into `com.github.braillesystems.learnbraille.res` package. -- Use `DslTest.kt` file as DSL tutorial. - -Information correctness should be checked in compile-time or during app initialization runtime as much as possible. If some additional info is needed, do not hardcode it. Just request the new DSL feature via GitHub Issues. - -Adding rules, prevent lambda of capturing context that will be invalid next time the fragment entered, so use `Fragment.getString` outside of lambdas. - -#### Adding course - -1. Create lessons by `lessons` delegate. -2. Create a course in `CourseBuilder` and add lessons to it. - -Always use `com.github.braillesystems.learnbraille.res.content` value to get materials, they are indexed here in a proper way. - -#### Adding deck - -1. Add a new deck tag to `DeckTags`. -2. Map tag to deck's predicate in `DecksBuilder`. -3. Map deck's tag to user-visible string in `deckTagToName`. - -#### Adding materials - -1. Create materials by one of delegates: `markers` or `symbols`. -2. Add created materials to the `contens` (`materials` delegate). -3. Add to `inputSymbolPrintRules` and `showSymbolPrintRules`, or to `inputMarkerPrintRules` and `showMarkerPrintRules`. - -Symbols that are not from a particular alphabet and do not exist on the classical American keyboard should be treated as special and be added via `enum class`. - -New materials can be marked as known by default in `knownMaterials` (`known` delegate). - -## Database - -Database scheme is described [here](https://github.com/braille-systems/learn-braille/blob/master/database.md). +# Contributing to Learn Braille + +## Build + +To work with the project you need `android-studio >= 3.3`, you can get it [here](https:://developer.android.com/studio). + +For building and running the app, your either need to download a virtual Android device image (this can be done from within the Android Studio) or connect a physical device (with developer options enabled and USB debugging turned on) via USB cable. + +## Git workflow + +- Write short and informative commit messages. Mention the issue you're working on (begins with `#`), e. g. `implement something(#0)`. [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/). +- Follow [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model/). + +## Coding style + +- Kotlin [coding conventions](https://kotlinlang.org/docs/reference/coding-conventions.html) + Java [google style guides](https://google.github.io/styleguide/javaguide.html) and [oracle](https://www.oracle.com/technetwork/java/codeconvtoc-136057.html). +- Also look around each time you do something new to see, how such a thing was formatted and implemented before. +- Set up Android Studio proper Kotlin code style `editor -> code style -> kotlin -> set from -> predefined -> Kotlin style guide`. +- Apply autoformatting to edited files each time before commit. + +## Adding content + +There is a handy DSL that allows writing content in a typesafe way. + +- All app content should be placed into `com.github.braillesystems.learnbraille.res` package. +- Use `DslTest.kt` file as DSL tutorial. + +Information correctness should be checked in compile-time or during app initialization runtime as much as possible. If some additional info is needed, do not hardcode it. Just request the new DSL feature via GitHub Issues. + +Adding rules, prevent lambda of capturing context that will be invalid next time the fragment entered, so use `Fragment.getString` outside of lambdas. + +#### Adding course + +1. Create lessons by `lessons` delegate. +2. Create a course in `CourseBuilder` and add lessons to it. + +Always use `com.github.braillesystems.learnbraille.res.content` value to get materials, they are indexed here in a proper way. + +#### Adding deck + +1. Add a new deck tag to `DeckTags`. +2. Map tag to deck's predicate in `DecksBuilder`. +3. Map deck's tag to user-visible string in `deckTagToName`. + +#### Adding materials + +1. Create materials by one of delegates: `markers` or `symbols`. +2. Add created materials to the `contens` (`materials` delegate). +3. Add to `inputSymbolPrintRules` and `showSymbolPrintRules`, or to `inputMarkerPrintRules` and `showMarkerPrintRules`. + +Symbols that are not from a particular alphabet and do not exist on the classical American keyboard should be treated as special and be added via `enum class`. + +New materials can be marked as known by default in `knownMaterials` (`known` delegate). + +## Database + +Database scheme is described [here](https://github.com/braille-systems/learn-braille/blob/master/database.md). diff --git a/README.md b/README.md index 4ad82ddf..8e89fe57 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,33 @@ -# Learn Braille - -[![Actions Status](https://github.com/braille-systems/learn-braille/workflows/Android%20CI/badge.svg)](https://github.com/braille-systems/learn-braille/actions) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - -Learn Braille is an Android application for teaching the tactile writing system created -by Louis Braille. - -It is one of the few apps primarily designed for the Russian Braille system, -but also other systems could be easily added. - -The app can be used with -[Braille Trainer](https://github.com/braille-systems/braille-trainer) -and [tiles](https://github.com/braille-systems/braille-tiles) -or without them. - -Feel free to [contribute](https://github.com/braille-systems/learn-braille/blob/master/CONTRIBUTING.md)!
-Take a look at [wiki pages (Russian)](https://github.com/braille-systems/learn-braille/wiki)
- - -## User's Guidelines - -The app is [available](https://play.google.com/store/apps/details?id=com.github.braillesystems.learnbraille&hl=ru) in the Google Play. - -### System Requirements and limitations - -To successfully run this application, your smartphone or tablet PC must satisfy the following conditions: -- Android 4.4 KitKat or higher (`5.1` is required for `Google TalkBack` optimizations). -- Screen of size not less than 4 inches. - -For accessibility, you will require TalkBack service. -It is pre-installed by default on a majority of devices.
-For others, it is available in the Google Play. +# Learn Braille + +[![Actions Status](https://github.com/braille-systems/learn-braille/workflows/Android%20CI/badge.svg)](https://github.com/braille-systems/learn-braille/actions) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +Learn Braille is an Android application for teaching the tactile writing system created +by Louis Braille. + +It is one of the few apps primarily designed for the Russian Braille system, +but also other systems could be easily added. + +The app can be used with +[Braille Trainer](https://github.com/braille-systems/braille-trainer) +and [tiles](https://github.com/braille-systems/braille-tiles) +or without them. + +Feel free to [contribute](https://github.com/braille-systems/learn-braille/blob/master/CONTRIBUTING.md)!
+Take a look at [wiki pages (Russian)](https://github.com/braille-systems/learn-braille/wiki)
+ + +## User's Guidelines + +The app is [available](https://play.google.com/store/apps/details?id=com.github.braillesystems.learnbraille&hl=ru) in the Google Play. + +### System Requirements and limitations + +To successfully run this application, your smartphone or tablet PC must satisfy the following conditions: +- Android 4.4 KitKat or higher (`5.1` is required for `Google TalkBack` optimizations). +- Screen of size not less than 4 inches. + +For accessibility, you will require TalkBack service. +It is pre-installed by default on a majority of devices.
+For others, it is available in the Google Play. diff --git a/app/.gitignore b/app/.gitignore index 3543521e..796b96d1 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1 @@ -/build +/build diff --git a/app/app.iml b/app/app.iml index 2cc3fdd3..7329b281 100644 --- a/app/app.iml +++ b/app/app.iml @@ -1,258 +1,258 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 00aff17a..3068e2e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,122 +1,122 @@ -apply plugin: 'com.android.application' - -apply plugin: 'kotlin-android' - -apply plugin: 'kotlin-android-extensions' - -apply plugin: 'kotlin-kapt' - -apply plugin: 'androidx.navigation.safeargs' - -apply plugin: 'kotlinx-serialization' - -apply plugin: 'org.jetbrains.dokka' - -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - compileSdkVersion 29 - buildToolsVersion "29.0.3" - dataBinding { - enabled = true - } - defaultConfig { - applicationId "com.github.braillesystems.learnbraille" - minSdkVersion 19 - targetSdkVersion 29 - versionCode 14 - versionName "1.2.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary = true - multiDexEnabled = true - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - productFlavors { - } - dokka { - outputDirectory = "$buildDir/dokka" - packageOptions { - prefix = "android" - suppress = true - } - packageOptions{ - prefix = "androidx" - suppress = true - } - } - lintOptions { - abortOnError false - } -} - -dependencies { - def lifecycle_version = "2.2.0" - def koin_version = '2.1.5' - - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.core:core-ktx:1.0.2' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - testImplementation 'junit:junit:4.12' - - // Usb serial - implementation 'com.github.felHR85:UsbSerial:6.1.0' - - // Room - implementation "androidx.room:room-runtime:$version_room" - kapt "androidx.room:room-compiler:$version_room" - implementation "androidx.room:room-ktx:$version_room" - testImplementation "androidx.room:room-testing:$version_room" - implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" - - // Testing - androidTestImplementation 'androidx.test.ext:junit:1.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' - - // Navigation - implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1' - implementation 'androidx.navigation:navigation-ui-ktx:2.2.1' - - // Timber logging - implementation 'com.jakewharton.timber:timber:4.7.1' - - // Lifecycle - implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" - - //Design - implementation 'com.google.android.material:material:1.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0' - - // Serialization - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // or "kotlin-stdlib-jdk8" - // Downgraded to '0.14.0' because of https://github.com/Kotlin/kotlinx.serialization/issues/576 - implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" // JVM dependency - - // Koin - implementation "org.koin:koin-core:$koin_version" - implementation "org.koin:koin-core-ext:$koin_version" - implementation "org.koin:koin-android:$koin_version" - implementation "org.koin:koin-android-viewmodel:$koin_version" - implementation "org.koin:koin-android-ext:$koin_version" - - // Many methods - implementation 'com.android.support:multidex:1.0.3' - - //Settings - implementation 'androidx.preference:preference:1.1.1' -} - -android { - lintOptions { - disable 'MissingTranslation' - } -} +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +apply plugin: 'kotlin-kapt' + +apply plugin: 'androidx.navigation.safeargs' + +apply plugin: 'kotlinx-serialization' + +apply plugin: 'org.jetbrains.dokka' + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + compileSdkVersion 29 + buildToolsVersion "29.0.3" + dataBinding { + enabled = true + } + defaultConfig { + applicationId "com.github.braillesystems.learnbraille" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 14 + versionName "1.2.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + multiDexEnabled = true + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + dokka { + outputDirectory = "$buildDir/dokka" + packageOptions { + prefix = "android" + suppress = true + } + packageOptions{ + prefix = "androidx" + suppress = true + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + def lifecycle_version = "2.2.0" + def koin_version = '2.1.5' + + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.12' + + // Usb serial + implementation 'com.github.felHR85:UsbSerial:6.1.0' + + // Room + implementation "androidx.room:room-runtime:$version_room" + kapt "androidx.room:room-compiler:$version_room" + implementation "androidx.room:room-ktx:$version_room" + testImplementation "androidx.room:room-testing:$version_room" + implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" + + // Testing + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + // Navigation + implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1' + implementation 'androidx.navigation:navigation-ui-ktx:2.2.1' + + // Timber logging + implementation 'com.jakewharton.timber:timber:4.7.1' + + // Lifecycle + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + + //Design + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + + // Serialization + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // or "kotlin-stdlib-jdk8" + // Downgraded to '0.14.0' because of https://github.com/Kotlin/kotlinx.serialization/issues/576 + implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" // JVM dependency + + // Koin + implementation "org.koin:koin-core:$koin_version" + implementation "org.koin:koin-core-ext:$koin_version" + implementation "org.koin:koin-android:$koin_version" + implementation "org.koin:koin-android-viewmodel:$koin_version" + implementation "org.koin:koin-android-ext:$koin_version" + + // Many methods + implementation 'com.android.support:multidex:1.0.3' + + //Settings + implementation 'androidx.preference:preference:1.1.1' +} + +android { + lintOptions { + disable 'MissingTranslation' + } +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6e7ffa99..f1b42451 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,21 +1,21 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabaseTest.kt b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabaseTest.kt index f67a9ab5..b97aea4a 100644 --- a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabaseTest.kt +++ b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabaseTest.kt @@ -1,183 +1,183 @@ -package com.github.braillesystems.learnbraille.data.db - -import androidx.room.Room -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.github.braillesystems.learnbraille.data.entities.* -import com.github.braillesystems.learnbraille.data.entities.BrailleDot.E -import com.github.braillesystems.learnbraille.data.entities.BrailleDot.F -import com.github.braillesystems.learnbraille.res.SymbolType -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.io.IOException - -@RunWith(AndroidJUnit4::class) -class LearnBrailleDatabaseTest { - - private lateinit var db: LearnBrailleDatabase - - private val users = listOf( - User( - login = "default", - name = "John Smith" - ) - ) - private val materials = listOf( - Material( - 1, Symbol( - char = 'А', - brailleDots = BrailleDots(F, E, E, E, E, E), - type = SymbolType.ru - ) - ) - ) - private val decks = listOf( - Deck( - id = 1, - tag = "Ru letters" - ) - ) - private val cards = listOf( - Card( - deckId = 1, - materialId = 1 - ) - ) - private val courses = listOf( - Course( - id = 1, - name = "Super course", - description = "Oh, it's so good" - ) - ) - private val lessons = listOf( - Lesson( - id = 1, - name = "First", - description = "First First First", - courseId = 1 - ), - Lesson( - id = 2, - name = "Last", - description = "Last Last Last", - courseId = 1 - ) - ) - private val steps = listOf( - Step( - id = 1, - data = FirstInfo("FirstInfo"), - lessonId = 1, courseId = 1 - ), - Step( - id = 2, - data = Info("Open your book"), - lessonId = 1, courseId = 1 - ), - Step( - id = 3, - data = ShowDots( - text = "Перед Вами полное шеститочие", - brailleDots = BrailleDots(F, F, F, F, F, F) - ), - lessonId = 1, courseId = 1 - ), - Step( - id = 4, - data = InputDots( - text = "Введите все шесть точек", - brailleDots = BrailleDots(F, F, F, F, F, F) - ), - lessonId = 2, courseId = 1 - ), - Step( - id = 5, - data = Show(materials.first()), - lessonId = 2, courseId = 1 - ), - Step( - id = 6, - data = Input(materials.first()), - lessonId = 2, courseId = 1 - ), - Step( - id = 7, - data = LastInfo("LastInfo"), - lessonId = 2, courseId = 1 - ) - ) - private val annotations = listOf( - StepAnnotation(id = 1, name = "a1"), - StepAnnotation(id = 2, name = "a2") - ) - private val stepAnnotations = listOf( - StepHasAnnotation( - courseId = 1, - lessonId = 3, - stepId = 2, - annotationId = 1 - ) - ) - - @Before - fun createDB() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - db = Room - .inMemoryDatabaseBuilder(context, LearnBrailleDatabase::class.java) - .allowMainThreadQueries() - .build().apply { - runBlocking { - userDao.insert(users) - materialDao.insert(materials) - deckDao.insert(decks) - cardDao.insert(cards) - courseDao.insert(courses) - lessonDao.insert(lessons) - stepDao.insert(steps) - stepAnnotationDao.insert(annotations) - stepHasAnnotationDao.insert(stepAnnotations) - } - } - } - - @After - @Throws(IOException::class) - fun closeDB() { - db.close() - } - - @Test - fun testUsers() = runBlocking { - assertEquals("default", db.userDao.user(1)!!.login) - } - - @Test - fun testMaterials() = runBlocking { - val data = db.materialDao.material(1)!!.data - require(data is Symbol) - assertEquals(BrailleDots(F, E, E, E, E, E), data.brailleDots) - } - - @Test - fun testDecks() = runBlocking { - assertEquals("Ru letters", db.deckDao.deck(1)!!.tag) - } - - @Test - fun testCourses() = runBlocking { - assertEquals("Super course", db.courseDao.course(1)!!.name) - } - - @Test - fun testSteps() = runBlocking { - for ((i, step) in steps.withIndex()) { - val fromDb = db.stepDao.step(i + 1L)!! - assertEquals(step, fromDb) - } - } -} +package com.github.braillesystems.learnbraille.data.db + +import androidx.room.Room +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.github.braillesystems.learnbraille.data.entities.* +import com.github.braillesystems.learnbraille.data.entities.BrailleDot.E +import com.github.braillesystems.learnbraille.data.entities.BrailleDot.F +import com.github.braillesystems.learnbraille.res.SymbolType +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class LearnBrailleDatabaseTest { + + private lateinit var db: LearnBrailleDatabase + + private val users = listOf( + User( + login = "default", + name = "John Smith" + ) + ) + private val materials = listOf( + Material( + 1, Symbol( + char = 'А', + brailleDots = BrailleDots(F, E, E, E, E, E), + type = SymbolType.ru + ) + ) + ) + private val decks = listOf( + Deck( + id = 1, + tag = "Ru letters" + ) + ) + private val cards = listOf( + Card( + deckId = 1, + materialId = 1 + ) + ) + private val courses = listOf( + Course( + id = 1, + name = "Super course", + description = "Oh, it's so good" + ) + ) + private val lessons = listOf( + Lesson( + id = 1, + name = "First", + description = "First First First", + courseId = 1 + ), + Lesson( + id = 2, + name = "Last", + description = "Last Last Last", + courseId = 1 + ) + ) + private val steps = listOf( + Step( + id = 1, + data = FirstInfo("FirstInfo"), + lessonId = 1, courseId = 1 + ), + Step( + id = 2, + data = Info("Open your book"), + lessonId = 1, courseId = 1 + ), + Step( + id = 3, + data = ShowDots( + text = "Перед Вами полное шеститочие", + brailleDots = BrailleDots(F, F, F, F, F, F) + ), + lessonId = 1, courseId = 1 + ), + Step( + id = 4, + data = InputDots( + text = "Введите все шесть точек", + brailleDots = BrailleDots(F, F, F, F, F, F) + ), + lessonId = 2, courseId = 1 + ), + Step( + id = 5, + data = Show(materials.first()), + lessonId = 2, courseId = 1 + ), + Step( + id = 6, + data = Input(materials.first()), + lessonId = 2, courseId = 1 + ), + Step( + id = 7, + data = LastInfo("LastInfo"), + lessonId = 2, courseId = 1 + ) + ) + private val annotations = listOf( + StepAnnotation(id = 1, name = "a1"), + StepAnnotation(id = 2, name = "a2") + ) + private val stepAnnotations = listOf( + StepHasAnnotation( + courseId = 1, + lessonId = 3, + stepId = 2, + annotationId = 1 + ) + ) + + @Before + fun createDB() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + db = Room + .inMemoryDatabaseBuilder(context, LearnBrailleDatabase::class.java) + .allowMainThreadQueries() + .build().apply { + runBlocking { + userDao.insert(users) + materialDao.insert(materials) + deckDao.insert(decks) + cardDao.insert(cards) + courseDao.insert(courses) + lessonDao.insert(lessons) + stepDao.insert(steps) + stepAnnotationDao.insert(annotations) + stepHasAnnotationDao.insert(stepAnnotations) + } + } + } + + @After + @Throws(IOException::class) + fun closeDB() { + db.close() + } + + @Test + fun testUsers() = runBlocking { + assertEquals("default", db.userDao.user(1)!!.login) + } + + @Test + fun testMaterials() = runBlocking { + val data = db.materialDao.material(1)!!.data + require(data is Symbol) + assertEquals(BrailleDots(F, E, E, E, E, E), data.brailleDots) + } + + @Test + fun testDecks() = runBlocking { + assertEquals("Ru letters", db.deckDao.deck(1)!!.tag) + } + + @Test + fun testCourses() = runBlocking { + assertEquals("Super course", db.courseDao.course(1)!!.name) + } + + @Test + fun testSteps() = runBlocking { + for ((i, step) in steps.withIndex()) { + val fromDb = db.stepDao.step(i + 1L)!! + assertEquals(step, fromDb) + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a937f5c..4373866c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,27 +1,27 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt b/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt index 51dc64a5..4615ba07 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt @@ -1,122 +1,122 @@ -package com.github.braillesystems.learnbraille - -import android.app.Application -import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase -import com.github.braillesystems.learnbraille.data.dsl.UsersCourse -import com.github.braillesystems.learnbraille.data.entities.BrailleDots -import com.github.braillesystems.learnbraille.data.repository.* -import com.github.braillesystems.learnbraille.ui.screens.practice.CardViewModelFactory -import com.github.braillesystems.learnbraille.utils.devnull -import org.koin.android.ext.android.get -import org.koin.android.ext.koin.androidContext -import org.koin.core.Koin -import org.koin.core.context.startKoin -import org.koin.dsl.module -import timber.log.Timber - -class LearnBrailleApplication : Application() { - - override fun onCreate() { - super.onCreate() - Timber.plant(Timber.DebugTree()) - Timber.i("onCreate") - - val koinModule = module { - - single { LearnBrailleDatabase.buildDatabase(this@LearnBrailleApplication) } - - factory { - ActionsRepositoryImpl(get().actionDao) - } - factory { - ActionsRepositoryImpl(get().actionDao) - } - - factory { - PreferenceRepositoryImpl( - this@LearnBrailleApplication, - get().userDao - ) - } - factory { - PreferenceRepositoryImpl( - this@LearnBrailleApplication, - get().userDao - ) - } - - factory { - val db = get() - MaterialsRepositoryImpl(db.deckDao, db.cardDao, get()) - } - - factory { - val db = get() - PracticeRepositoryImpl( - this@LearnBrailleApplication, - db.deckDao, get(), get() - ) - } - factory { - val db = get() - PracticeRepositoryImpl( - this@LearnBrailleApplication, - db.deckDao, get(), get() - ) - } - - factory { - BrowserRepositoryImpl( - this@LearnBrailleApplication, - get(), get() - ) - } - factory { - BrowserRepositoryImpl( - this@LearnBrailleApplication, - get(), get() - ) - } - - factory { - get().run { - TheoryRepositoryImpl( - lessonDao, stepDao, - currentStepDao, lastCourseStepDao, lastLessonStepDao, knownMaterialDao, - get(), get() - ) - } - } - factory { - get().run { - TheoryRepositoryImpl( - lessonDao, stepDao, - currentStepDao, lastCourseStepDao, lastLessonStepDao, knownMaterialDao, - get(), get() - ) - } - } - - factory { (getEnteredDots: () -> BrailleDots) -> - CardViewModelFactory( - get(), get(), get(), - this@LearnBrailleApplication, - getEnteredDots - ) - } - } - - koin = startKoin { - androidContext(this@LearnBrailleApplication) - modules(koinModule) - }.koin - - // Touch database to force it's preparation - get().devnull - } -} - -lateinit var koin: Koin - private set - -val COURSE = UsersCourse +package com.github.braillesystems.learnbraille + +import android.app.Application +import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase +import com.github.braillesystems.learnbraille.data.dsl.UsersCourse +import com.github.braillesystems.learnbraille.data.entities.BrailleDots +import com.github.braillesystems.learnbraille.data.repository.* +import com.github.braillesystems.learnbraille.ui.screens.practice.CardViewModelFactory +import com.github.braillesystems.learnbraille.utils.devnull +import org.koin.android.ext.android.get +import org.koin.android.ext.koin.androidContext +import org.koin.core.Koin +import org.koin.core.context.startKoin +import org.koin.dsl.module +import timber.log.Timber + +class LearnBrailleApplication : Application() { + + override fun onCreate() { + super.onCreate() + Timber.plant(Timber.DebugTree()) + Timber.i("onCreate") + + val koinModule = module { + + single { LearnBrailleDatabase.buildDatabase(this@LearnBrailleApplication) } + + factory { + ActionsRepositoryImpl(get().actionDao) + } + factory { + ActionsRepositoryImpl(get().actionDao) + } + + factory { + PreferenceRepositoryImpl( + this@LearnBrailleApplication, + get().userDao + ) + } + factory { + PreferenceRepositoryImpl( + this@LearnBrailleApplication, + get().userDao + ) + } + + factory { + val db = get() + MaterialsRepositoryImpl(db.deckDao, db.cardDao, get()) + } + + factory { + val db = get() + PracticeRepositoryImpl( + this@LearnBrailleApplication, + db.deckDao, get(), get() + ) + } + factory { + val db = get() + PracticeRepositoryImpl( + this@LearnBrailleApplication, + db.deckDao, get(), get() + ) + } + + factory { + BrowserRepositoryImpl( + this@LearnBrailleApplication, + get(), get() + ) + } + factory { + BrowserRepositoryImpl( + this@LearnBrailleApplication, + get(), get() + ) + } + + factory { + get().run { + TheoryRepositoryImpl( + lessonDao, stepDao, + currentStepDao, lastCourseStepDao, lastLessonStepDao, knownMaterialDao, + get(), get() + ) + } + } + factory { + get().run { + TheoryRepositoryImpl( + lessonDao, stepDao, + currentStepDao, lastCourseStepDao, lastLessonStepDao, knownMaterialDao, + get(), get() + ) + } + } + + factory { (getEnteredDots: () -> BrailleDots) -> + CardViewModelFactory( + get(), get(), get(), + this@LearnBrailleApplication, + getEnteredDots + ) + } + } + + koin = startKoin { + androidContext(this@LearnBrailleApplication) + modules(koinModule) + }.koin + + // Touch database to force it's preparation + get().devnull + } +} + +lateinit var koin: Koin + private set + +val COURSE = UsersCourse diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabase.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabase.kt index 3f52dad7..75329794 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabase.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/db/LearnBrailleDatabase.kt @@ -1,327 +1,327 @@ -package com.github.braillesystems.learnbraille.data.db - -import android.annotation.SuppressLint -import android.content.ContentValues -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase -import com.github.braillesystems.learnbraille.data.entities.* -import com.github.braillesystems.learnbraille.res.prepopulationData -import com.github.braillesystems.learnbraille.utils.DateConverters -import com.github.braillesystems.learnbraille.utils.devnull -import com.github.braillesystems.learnbraille.utils.logged -import com.github.braillesystems.learnbraille.utils.scope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.koin.core.KoinComponent -import org.koin.core.get -import timber.log.Timber - -@Database( - entities = - [ - User::class, Material::class, KnownMaterial::class, - Deck::class, Card::class, - Course::class, Lesson::class, Step::class, StepAnnotation::class, StepHasAnnotation::class, - CurrentStep::class, LastCourseStep::class, LastLessonStep::class, - Action::class - ], - version = 19, - exportSchema = true -) -@TypeConverters( - BrailleDotsConverters::class, - MaterialDataTypeConverters::class, - StepDataConverters::class, - ActionTypeConverters::class, - DateConverters::class -) -abstract class LearnBrailleDatabase : RoomDatabase(), KoinComponent { - - abstract val userDao: UserDao - abstract val materialDao: MaterialDao - abstract val knownMaterialDao: KnownMaterialDao - - abstract val deckDao: DeckDao - abstract val cardDao: CardDao - - abstract val courseDao: CourseDao - abstract val lessonDao: LessonDao - abstract val stepDao: StepDao - abstract val stepAnnotationDao: StepAnnotationDao - abstract val stepHasAnnotationDao: StepHasAnnotationDao - - abstract val currentStepDao: CurrentStepDao - abstract val lastCourseStepDao: LastCourseStepDao - abstract val lastLessonStepDao: LastLessonStepDao - - abstract val actionDao: ActionDao - - @Volatile - private lateinit var prepareDbJob: Job - - /** - * Android Room prepopulation and migrations are lazy, - * they will start with the first request, blocking it. - */ - private fun init(): LearnBrailleDatabase = this.also { - prepareDbJob = scope().launch { - Timber.i("Requesting value from database to force database callbacks and migrations") - Timber.i("Start database preparation") - userDao.user(1).devnull - Timber.i("Finnish database preparation") - } - } - - val isInitialized: Boolean by logged { - prepareDbJob.isCompleted - } - - companion object { - - const val name = "learn_braille_database" - - /** - * Try to run `buildDatabase` before first user's request (mb in Application's `onCreate`) - * to make DB likely prepared until it is really needed. - */ - fun buildDatabase(context: Context) = Room - .databaseBuilder( - context.applicationContext, - LearnBrailleDatabase::class.java, - name - ) - .addCallback(object : Callback(), KoinComponent { - - @SuppressLint("SyntheticAccessor") - override fun onCreate(db: SupportSQLiteDatabase) { - super.onCreate(db) - Timber.d("onCreate") - prepopulate() - } - - override fun onDestructiveMigration(db: SupportSQLiteDatabase) { - super.onDestructiveMigration(db) - Timber.i("onDestructiveMigration") - prepopulate() - } - - private fun prepopulate() { - Timber.i("Prepopulate DB") - get().apply { - scope(prepareDbJob).launch { - prepopulationData.run { - users?.let { userDao.insert(it) } - materials?.let { materialDao.insert(it) } - decks?.let { deckDao.insert(it) } - cards?.let { cardDao.insert(it) } - courses?.let { courseDao.insert(it) } - lessons?.let { lessonDao.insert(it) } - steps?.let { stepDao.insert(it) } - stepAnnotations?.let { stepAnnotationDao.insert(it) } - stepsHasAnnotations?.let { stepHasAnnotationDao.insert(it) } - knownMaterials?.let { knownMaterialDao.insert(it) } - } - } - } - } - }) - .addMigrations( - MIGRATION_16_17, - MIGRATION_17_18, - MIGRATION_18_19 - ) - .build() - .init() - } -} - -private val MIGRATION_16_17 = object : Migration(16, 17), KoinComponent { - override fun migrate(database: SupportSQLiteDatabase) { - Timber.i("Start 16-17 migration") - - database.execSQL("delete from materials") - database.execSQL("delete from steps") - database.execSQL("delete from step_has_annotations") - - Timber.i("Removed old content") - - prepopulationData.run { - materials?.forEach { - database.insert( - "materials", - SQLiteDatabase.CONFLICT_IGNORE, - it.run { - ContentValues().apply { - put("id", id) - put("data", MaterialDataTypeConverters().to(data)) - } - } - ) - } - Timber.i("Materials loaded") - - steps?.forEach { - database.insert( - "steps", - SQLiteDatabase.CONFLICT_IGNORE, - it.run { - ContentValues().apply { - put("id", id) - put("course_id", courseId) - put("lesson_id", lessonId) - put("data", StepDataConverters().to(data)) - } - } - ) - } - Timber.i("Steps loaded") - - stepsHasAnnotations?.forEach { - database.insert( - "step_has_annotations", - SQLiteDatabase.CONFLICT_IGNORE, - it.run { - ContentValues().apply { - put("course_id", courseId) - put("lesson_id", lessonId) - put("step_id", stepId) - put("annotation_id", annotationId) - } - } - ) - } - Timber.i("Steps-annotations mapping loaded") - } - } -} - -private val MIGRATION_17_18 = object : Migration(17, 18) { - override fun migrate(database: SupportSQLiteDatabase) { - Timber.i("Start 17-18 migration") - database.execSQL(Action.creationQuery) - Timber.i("Actions table created") - } -} - -private val MIGRATION_18_19 = object : Migration(18, 19) { - override fun migrate(database: SupportSQLiteDatabase) { - Timber.i("Start 18-19 migration") - - database.execSQL("delete from lessons") - database.execSQL("delete from steps") - database.execSQL("delete from decks") - database.execSQL("delete from cards") - database.execSQL("delete from materials") - database.execSQL("delete from step_has_annotations") - database.execSQL("delete from step_annotations") - - Timber.i("Old data removed") - - prepopulationData.run { - lessons?.forEach { - database.insert( - "lessons", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("id", id) - put("course_id", courseId) - put("name", name) - put("description", description) - } - } - ) - } - - steps?.forEach { - database.insert( - "steps", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("id", id) - put("course_id", courseId) - put("lesson_id", lessonId) - put("data", StepDataConverters().to(data)) - } - } - ) - } - - decks?.forEach { - database.insert( - "decks", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("id", id) - put("tag", tag) - } - } - ) - } - - cards?.forEach { - database.insert( - "cards", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("deck_id", deckId) - put("material_id", materialId) - } - } - ) - } - - materials?.forEach { - database.insert( - "materials", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("id", id) - put("data", MaterialDataTypeConverters().to(data)) - } - } - ) - } - - stepAnnotations?.forEach { - database.insert( - "step_annotations", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("id", id) - put("name", name) - } - } - ) - } - - stepsHasAnnotations?.forEach { - database.insert( - "step_has_annotations", - SQLiteDatabase.CONFLICT_ABORT, - it.run { - ContentValues().apply { - put("course_id", courseId) - put("lesson_id", lessonId) - put("step_id", stepId) - put("annotation_id", annotationId) - } - } - ) - } - } - - Timber.i("Finish 18-19 migration") - } -} +package com.github.braillesystems.learnbraille.data.db + +import android.annotation.SuppressLint +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.github.braillesystems.learnbraille.data.entities.* +import com.github.braillesystems.learnbraille.res.prepopulationData +import com.github.braillesystems.learnbraille.utils.DateConverters +import com.github.braillesystems.learnbraille.utils.devnull +import com.github.braillesystems.learnbraille.utils.logged +import com.github.braillesystems.learnbraille.utils.scope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import org.koin.core.KoinComponent +import org.koin.core.get +import timber.log.Timber + +@Database( + entities = + [ + User::class, Material::class, KnownMaterial::class, + Deck::class, Card::class, + Course::class, Lesson::class, Step::class, StepAnnotation::class, StepHasAnnotation::class, + CurrentStep::class, LastCourseStep::class, LastLessonStep::class, + Action::class + ], + version = 19, + exportSchema = true +) +@TypeConverters( + BrailleDotsConverters::class, + MaterialDataTypeConverters::class, + StepDataConverters::class, + ActionTypeConverters::class, + DateConverters::class +) +abstract class LearnBrailleDatabase : RoomDatabase(), KoinComponent { + + abstract val userDao: UserDao + abstract val materialDao: MaterialDao + abstract val knownMaterialDao: KnownMaterialDao + + abstract val deckDao: DeckDao + abstract val cardDao: CardDao + + abstract val courseDao: CourseDao + abstract val lessonDao: LessonDao + abstract val stepDao: StepDao + abstract val stepAnnotationDao: StepAnnotationDao + abstract val stepHasAnnotationDao: StepHasAnnotationDao + + abstract val currentStepDao: CurrentStepDao + abstract val lastCourseStepDao: LastCourseStepDao + abstract val lastLessonStepDao: LastLessonStepDao + + abstract val actionDao: ActionDao + + @Volatile + private lateinit var prepareDbJob: Job + + /** + * Android Room prepopulation and migrations are lazy, + * they will start with the first request, blocking it. + */ + private fun init(): LearnBrailleDatabase = this.also { + prepareDbJob = scope().launch { + Timber.i("Requesting value from database to force database callbacks and migrations") + Timber.i("Start database preparation") + userDao.user(1).devnull + Timber.i("Finnish database preparation") + } + } + + val isInitialized: Boolean by logged { + prepareDbJob.isCompleted + } + + companion object { + + const val name = "learn_braille_database" + + /** + * Try to run `buildDatabase` before first user's request (mb in Application's `onCreate`) + * to make DB likely prepared until it is really needed. + */ + fun buildDatabase(context: Context) = Room + .databaseBuilder( + context.applicationContext, + LearnBrailleDatabase::class.java, + name + ) + .addCallback(object : Callback(), KoinComponent { + + @SuppressLint("SyntheticAccessor") + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + Timber.d("onCreate") + prepopulate() + } + + override fun onDestructiveMigration(db: SupportSQLiteDatabase) { + super.onDestructiveMigration(db) + Timber.i("onDestructiveMigration") + prepopulate() + } + + private fun prepopulate() { + Timber.i("Prepopulate DB") + get().apply { + scope(prepareDbJob).launch { + prepopulationData.run { + users?.let { userDao.insert(it) } + materials?.let { materialDao.insert(it) } + decks?.let { deckDao.insert(it) } + cards?.let { cardDao.insert(it) } + courses?.let { courseDao.insert(it) } + lessons?.let { lessonDao.insert(it) } + steps?.let { stepDao.insert(it) } + stepAnnotations?.let { stepAnnotationDao.insert(it) } + stepsHasAnnotations?.let { stepHasAnnotationDao.insert(it) } + knownMaterials?.let { knownMaterialDao.insert(it) } + } + } + } + } + }) + .addMigrations( + MIGRATION_16_17, + MIGRATION_17_18, + MIGRATION_18_19 + ) + .build() + .init() + } +} + +private val MIGRATION_16_17 = object : Migration(16, 17), KoinComponent { + override fun migrate(database: SupportSQLiteDatabase) { + Timber.i("Start 16-17 migration") + + database.execSQL("delete from materials") + database.execSQL("delete from steps") + database.execSQL("delete from step_has_annotations") + + Timber.i("Removed old content") + + prepopulationData.run { + materials?.forEach { + database.insert( + "materials", + SQLiteDatabase.CONFLICT_IGNORE, + it.run { + ContentValues().apply { + put("id", id) + put("data", MaterialDataTypeConverters().to(data)) + } + } + ) + } + Timber.i("Materials loaded") + + steps?.forEach { + database.insert( + "steps", + SQLiteDatabase.CONFLICT_IGNORE, + it.run { + ContentValues().apply { + put("id", id) + put("course_id", courseId) + put("lesson_id", lessonId) + put("data", StepDataConverters().to(data)) + } + } + ) + } + Timber.i("Steps loaded") + + stepsHasAnnotations?.forEach { + database.insert( + "step_has_annotations", + SQLiteDatabase.CONFLICT_IGNORE, + it.run { + ContentValues().apply { + put("course_id", courseId) + put("lesson_id", lessonId) + put("step_id", stepId) + put("annotation_id", annotationId) + } + } + ) + } + Timber.i("Steps-annotations mapping loaded") + } + } +} + +private val MIGRATION_17_18 = object : Migration(17, 18) { + override fun migrate(database: SupportSQLiteDatabase) { + Timber.i("Start 17-18 migration") + database.execSQL(Action.creationQuery) + Timber.i("Actions table created") + } +} + +private val MIGRATION_18_19 = object : Migration(18, 19) { + override fun migrate(database: SupportSQLiteDatabase) { + Timber.i("Start 18-19 migration") + + database.execSQL("delete from lessons") + database.execSQL("delete from steps") + database.execSQL("delete from decks") + database.execSQL("delete from cards") + database.execSQL("delete from materials") + database.execSQL("delete from step_has_annotations") + database.execSQL("delete from step_annotations") + + Timber.i("Old data removed") + + prepopulationData.run { + lessons?.forEach { + database.insert( + "lessons", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("id", id) + put("course_id", courseId) + put("name", name) + put("description", description) + } + } + ) + } + + steps?.forEach { + database.insert( + "steps", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("id", id) + put("course_id", courseId) + put("lesson_id", lessonId) + put("data", StepDataConverters().to(data)) + } + } + ) + } + + decks?.forEach { + database.insert( + "decks", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("id", id) + put("tag", tag) + } + } + ) + } + + cards?.forEach { + database.insert( + "cards", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("deck_id", deckId) + put("material_id", materialId) + } + } + ) + } + + materials?.forEach { + database.insert( + "materials", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("id", id) + put("data", MaterialDataTypeConverters().to(data)) + } + } + ) + } + + stepAnnotations?.forEach { + database.insert( + "step_annotations", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("id", id) + put("name", name) + } + } + ) + } + + stepsHasAnnotations?.forEach { + database.insert( + "step_has_annotations", + SQLiteDatabase.CONFLICT_ABORT, + it.run { + ContentValues().apply { + put("course_id", courseId) + put("lesson_id", lessonId) + put("step_id", stepId) + put("annotation_id", annotationId) + } + } + ) + } + } + + Timber.i("Finish 18-19 migration") + } +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/BrailleDots.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/BrailleDots.kt index e62f0bd0..8aa8e47b 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/BrailleDots.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/BrailleDots.kt @@ -1,78 +1,78 @@ -package com.github.braillesystems.learnbraille.data.entities - -import androidx.room.TypeConverter -import com.github.braillesystems.learnbraille.data.entities.BrailleDot.E -import com.github.braillesystems.learnbraille.data.entities.BrailleDot.F -import kotlinx.serialization.Serializable - -/** - * State of one Braille dot. - */ -enum class BrailleDot { - E, // Empty - F; // Filled - - companion object Factories { - fun valueOf(b: Boolean) = if (b) F else E - fun valueOf(c: Char) = valueOf(c.toString()) - } -} - -/** - * Combination on Braille dots for one symbol in 6-dots notation. - */ -@Serializable -data class BrailleDots( - val b1: BrailleDot = E, val b2: BrailleDot = E, val b3: BrailleDot = E, - val b4: BrailleDot = E, val b5: BrailleDot = E, val b6: BrailleDot = E -) { - - constructor(dots: BooleanArray) : this( - dots.map(BrailleDot.Factories::valueOf) - ) - - constructor(dots: List) : this( - b1 = dots[0], - b2 = dots[1], - b3 = dots[2], - b4 = dots[3], - b5 = dots[4], - b6 = dots[5] - ) { - require(dots.size == 6) { - "Only 6 dots braille notation supported" - } - } - - constructor(string: String) : this( - string - .toCharArray() - .map(BrailleDot.Factories::valueOf) - ) - - override fun toString() = "$b1$b2$b3$b4$b5$b6" -} - -val BrailleDots.list: List - get() = listOf(b1, b2, b3, b4, b5, b6) - -val BrailleDots.spelling: String - get() = filled.joinToString(separator = ", ", transform = Int::toString) - -val BrailleDots.filled: List - get() = list - .mapIndexedNotNull { index, brailleDot -> - if (brailleDot == F) index + 1 - else null - } - -operator fun BrailleDots.contains(i: Int) = i in filled - -class BrailleDotsConverters { - - @TypeConverter - fun to(brailleDots: BrailleDots) = brailleDots.toString() - - @TypeConverter - fun from(data: String) = BrailleDots(data) -} +package com.github.braillesystems.learnbraille.data.entities + +import androidx.room.TypeConverter +import com.github.braillesystems.learnbraille.data.entities.BrailleDot.E +import com.github.braillesystems.learnbraille.data.entities.BrailleDot.F +import kotlinx.serialization.Serializable + +/** + * State of one Braille dot. + */ +enum class BrailleDot { + E, // Empty + F; // Filled + + companion object Factories { + fun valueOf(b: Boolean) = if (b) F else E + fun valueOf(c: Char) = valueOf(c.toString()) + } +} + +/** + * Combination on Braille dots for one symbol in 6-dots notation. + */ +@Serializable +data class BrailleDots( + val b1: BrailleDot = E, val b2: BrailleDot = E, val b3: BrailleDot = E, + val b4: BrailleDot = E, val b5: BrailleDot = E, val b6: BrailleDot = E +) { + + constructor(dots: BooleanArray) : this( + dots.map(BrailleDot.Factories::valueOf) + ) + + constructor(dots: List) : this( + b1 = dots[0], + b2 = dots[1], + b3 = dots[2], + b4 = dots[3], + b5 = dots[4], + b6 = dots[5] + ) { + require(dots.size == 6) { + "Only 6 dots braille notation supported" + } + } + + constructor(string: String) : this( + string + .toCharArray() + .map(BrailleDot.Factories::valueOf) + ) + + override fun toString() = "$b1$b2$b3$b4$b5$b6" +} + +val BrailleDots.list: List + get() = listOf(b1, b2, b3, b4, b5, b6) + +val BrailleDots.spelling: String + get() = filled.joinToString(separator = ", ", transform = Int::toString) + +val BrailleDots.filled: List + get() = list + .mapIndexedNotNull { index, brailleDot -> + if (brailleDot == F) index + 1 + else null + } + +operator fun BrailleDots.contains(i: Int) = i in filled + +class BrailleDotsConverters { + + @TypeConverter + fun to(brailleDots: BrailleDots) = brailleDots.toString() + + @TypeConverter + fun from(data: String) = BrailleDots(data) +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Lessons.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Lessons.kt index 15fa45c2..05a34c6a 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Lessons.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Lessons.kt @@ -1,34 +1,34 @@ -package com.github.braillesystems.learnbraille.data.entities - -import androidx.room.* - -typealias LessonName = String -typealias LessonDesc = String - -@Entity(tableName = "lessons", primaryKeys = ["id", "course_id"]) -data class Lesson( - val id: DBid, - @ColumnInfo(name = "course_id") - val courseId: DBid, - val name: LessonName, - val description: LessonDesc -) - -@Dao -interface LessonDao { - - @Insert - suspend fun insert(lessons: List) - - @Query( - """ - select * from lessons - where course_id = :courseId - order by id - """ - ) - suspend fun allCourseLessons(courseId: DBid): List - - @Query("delete from lessons") - suspend fun clear() -} +package com.github.braillesystems.learnbraille.data.entities + +import androidx.room.* + +typealias LessonName = String +typealias LessonDesc = String + +@Entity(tableName = "lessons", primaryKeys = ["id", "course_id"]) +data class Lesson( + val id: DBid, + @ColumnInfo(name = "course_id") + val courseId: DBid, + val name: LessonName, + val description: LessonDesc +) + +@Dao +interface LessonDao { + + @Insert + suspend fun insert(lessons: List) + + @Query( + """ + select * from lessons + where course_id = :courseId + order by id + """ + ) + suspend fun allCourseLessons(courseId: DBid): List + + @Query("delete from lessons") + suspend fun clear() +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Users.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Users.kt index 995c0ea4..73cbfcb0 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Users.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Users.kt @@ -1,33 +1,33 @@ -package com.github.braillesystems.learnbraille.data.entities - -import androidx.room.* - -typealias UserLogin = String -typealias UserName = String - -@Entity(tableName = "users", indices = [Index(value = ["login"], unique = true)]) -data class User( - @PrimaryKey(autoGenerate = true) - var id: DBid = 0, - val login: UserLogin, - val name: UserName -) - -@Dao -interface UserDao { - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insert(user: User) - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insert(users: List) - - @Query("select * from users where :login = login limit 1") - suspend fun user(login: UserLogin): User? - - @Query("select * from users where :id = id limit 1") - suspend fun user(id: DBid): User? - - @Query("delete from users") - suspend fun clear() -} +package com.github.braillesystems.learnbraille.data.entities + +import androidx.room.* + +typealias UserLogin = String +typealias UserName = String + +@Entity(tableName = "users", indices = [Index(value = ["login"], unique = true)]) +data class User( + @PrimaryKey(autoGenerate = true) + var id: DBid = 0, + val login: UserLogin, + val name: UserName +) + +@Dao +interface UserDao { + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(user: User) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(users: List) + + @Query("select * from users where :login = login limit 1") + suspend fun user(login: UserLogin): User? + + @Query("select * from users where :id = id limit 1") + suspend fun user(id: DBid): User? + + @Query("delete from users") + suspend fun clear() +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/MainActivity.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/MainActivity.kt index 7ba77c61..69bfd0e8 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/MainActivity.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/MainActivity.kt @@ -1,37 +1,37 @@ -package com.github.braillesystems.learnbraille.ui.screens - -import android.content.pm.ActivityInfo -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.NavController -import androidx.navigation.findNavController -import androidx.navigation.ui.NavigationUI -import com.github.braillesystems.learnbraille.R -import com.github.braillesystems.learnbraille.ui.brailletrainer.BrailleTrainer -import timber.log.Timber - -class MainActivity : AppCompatActivity() { - - private lateinit var navController: NavController - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - Timber.i("onCreate") - setContentView(R.layout.activity_main) - - navController = findNavController(R.id.navHostFragment) - NavigationUI.setupActionBarWithNavController(this, navController) - - BrailleTrainer.init(this) - - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - - override fun onSupportNavigateUp(): Boolean = try { - navController.navigateUp() - } catch (e: IllegalArgumentException) { - Timber.e(e, "Multitouch navigation") - false - } -} +package com.github.braillesystems.learnbraille.ui.screens + +import android.content.pm.ActivityInfo +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import androidx.navigation.findNavController +import androidx.navigation.ui.NavigationUI +import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.ui.brailletrainer.BrailleTrainer +import timber.log.Timber + +class MainActivity : AppCompatActivity() { + + private lateinit var navController: NavController + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Timber.i("onCreate") + setContentView(R.layout.activity_main) + + navController = findNavController(R.id.navHostFragment) + NavigationUI.setupActionBarWithNavController(this, navController) + + BrailleTrainer.init(this) + + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + + override fun onSupportNavigateUp(): Boolean = try { + navController.navigateUp() + } catch (e: IllegalArgumentException) { + Timber.e(e, "Multitouch navigation") + false + } +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/exit/ExitFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/exit/ExitFragment.kt index 9506a7df..ef5864ef 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/exit/ExitFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/exit/ExitFragment.kt @@ -1,41 +1,41 @@ -package com.github.braillesystems.learnbraille.ui.screens.exit - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import com.github.braillesystems.learnbraille.R -import com.github.braillesystems.learnbraille.databinding.FragmentExitBinding -import com.github.braillesystems.learnbraille.ui.screens.AbstractFragment -import com.github.braillesystems.learnbraille.utils.checkedAnnounce -import com.github.braillesystems.learnbraille.utils.navigate -import com.github.braillesystems.learnbraille.utils.navigateToLauncher -import com.github.braillesystems.learnbraille.utils.title - -class ExitFragment : AbstractFragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DataBindingUtil.inflate( - inflater, - R.layout.fragment_exit, - container, - false - ).ini().apply { - - title = getString(R.string.exit_question) - checkedAnnounce(title) - - exitButton.setOnClickListener { - navigate(R.id.action_global_menuFragment) - navigateToLauncher() - } - - continueButton.setOnClickListener { - navigate(R.id.action_global_menuFragment) - } - - }.root -} +package com.github.braillesystems.learnbraille.ui.screens.exit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.databinding.FragmentExitBinding +import com.github.braillesystems.learnbraille.ui.screens.AbstractFragment +import com.github.braillesystems.learnbraille.utils.checkedAnnounce +import com.github.braillesystems.learnbraille.utils.navigate +import com.github.braillesystems.learnbraille.utils.navigateToLauncher +import com.github.braillesystems.learnbraille.utils.title + +class ExitFragment : AbstractFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = DataBindingUtil.inflate( + inflater, + R.layout.fragment_exit, + container, + false + ).ini().apply { + + title = getString(R.string.exit_question) + checkedAnnounce(title) + + exitButton.setOnClickListener { + navigate(R.id.action_global_menuFragment) + navigateToLauncher() + } + + continueButton.setOnClickListener { + navigate(R.id.action_global_menuFragment) + } + + }.root +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/help/HelpFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/help/HelpFragment.kt index 23eb22a9..120c8b22 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/help/HelpFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/help/HelpFragment.kt @@ -1,33 +1,33 @@ -package com.github.braillesystems.learnbraille.ui.screens.help - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import com.github.braillesystems.learnbraille.R -import com.github.braillesystems.learnbraille.databinding.FragmentHelpBinding -import com.github.braillesystems.learnbraille.ui.screens.AbstractFragment -import com.github.braillesystems.learnbraille.utils.checkedAnnounce -import com.github.braillesystems.learnbraille.utils.getFragmentStringArg -import com.github.braillesystems.learnbraille.utils.removeHtmlMarkup - -class HelpFragment : AbstractFragment() { - - private val helpMessageArgName = "help_message" - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ) = DataBindingUtil.inflate( - inflater, - R.layout.fragment_help, - container, - false - ).ini().apply { - - val content = getFragmentStringArg(helpMessageArgName) - helpView.setSeparatedText(content) - checkedAnnounce(content.removeHtmlMarkup()) - - }.root -} +package com.github.braillesystems.learnbraille.ui.screens.help + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.databinding.FragmentHelpBinding +import com.github.braillesystems.learnbraille.ui.screens.AbstractFragment +import com.github.braillesystems.learnbraille.utils.checkedAnnounce +import com.github.braillesystems.learnbraille.utils.getFragmentStringArg +import com.github.braillesystems.learnbraille.utils.removeHtmlMarkup + +class HelpFragment : AbstractFragment() { + + private val helpMessageArgName = "help_message" + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ) = DataBindingUtil.inflate( + inflater, + R.layout.fragment_help, + container, + false + ).ini().apply { + + val content = getFragmentStringArg(helpMessageArgName) + helpView.setSeparatedText(content) + checkedAnnounce(content.removeHtmlMarkup()) + + }.root +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt index 5a9e2ae9..1488508d 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt @@ -1,162 +1,162 @@ -package com.github.braillesystems.learnbraille.ui.screens.menu - -import android.app.Activity.RESULT_OK -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.databinding.DataBindingUtil -import com.github.braillesystems.learnbraille.COURSE -import com.github.braillesystems.learnbraille.R -import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase -import com.github.braillesystems.learnbraille.databinding.FragmentMenuBinding -import com.github.braillesystems.learnbraille.ui.screens.AbstractFragmentWithHelp -import com.github.braillesystems.learnbraille.ui.screens.theory.toLastCourseStep -import com.github.braillesystems.learnbraille.utils.* -import com.google.android.material.button.MaterialButton -import org.koin.android.ext.android.inject -import timber.log.Timber - -class MenuFragment : AbstractFragmentWithHelp(R.string.menu_help) { - - private val db: LearnBrailleDatabase by inject() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DataBindingUtil.inflate( - inflater, - R.layout.fragment_menu, - container, - false - ).ini().also { binding -> - - title = getString(R.string.menu_actionbar_text_template).format(appName) - - val buttons = mutableListOf() - - binding.lessonsButton.also { - buttons += it - }.setOnClickListener(interruptingOnClickListener { - toLastCourseStep(COURSE.id) - }) - - binding.practiceButton.also { - buttons += it - }.setOnClickListener(interruptingOnClickListener { - navigate(R.id.action_menuFragment_to_practiceFragment) - }) - - binding.browserButton.also { - buttons += it - }.setOnClickListener(interruptingOnClickListener { - navigate(R.id.action_menuFragment_to_browserFragment) - }) - - binding.statsButton.also { - buttons += it - }.setOnClickListener(interruptingOnClickListener { - navigate(R.id.action_menuFragment_to_statsFragment) - }) - - if (preferenceRepository.additionalQrCodeButtonEnabled) { - binding.qrPracticeButton.also { - buttons += it - }.setOnClickListener { - try { - val intent = Intent("com.google.zxing.client.android.SCAN") - intent.putExtra("SCAN_MODE", "QR_CODE_MODE") - startActivityForResult(intent, qrRequestCode) - } catch (e: ActivityNotFoundException) { - checkedToast(getString(R.string.qr_intent_cancelled)) - sendMarketIntent("com.google.zxing.client.android") - } - } - } else { - binding.qrPracticeButton.visibility = View.GONE - } - - binding.settingsButton.also { - buttons += it - }.setOnClickListener { - navigate(R.id.action_menuFragment_to_settingsFragment) - } - - if (preferenceRepository.extendedAccessibilityEnabled) { - binding.exitButton.also { - buttons += it - }.setOnClickListener { - navigate(R.id.action_menuFragment_to_exitFragment) - } - } else { - binding.exitButton.visibility = View.GONE - } - - colorButtons(buttons) - - }.root - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - qrRequestCode -> processQrResult(resultCode, data) - } - } - - private fun processQrResult(resultCode: Int, data: Intent?) { - when (resultCode) { - RESULT_OK -> toast( - data?.getStringExtra("SCAN_RESULT") - ?: getString(R.string.menu_qr_empty_result).also { - Timber.e("QR: empty result with OK code") - } - ) - } - } - - private fun interruptingOnClickListener(block: (View) -> Unit) = - View.OnClickListener { - if (db.isInitialized) block(it) - else toast(getString(R.string.menu_db_not_initialized_warning)) - } - - private fun colorButtons(buttons: List) { - buttons - .filterIndexed { i, _ -> i % 2 == 0 } - .forEach { - it.setTextColor( - ContextCompat.getColor( - application, R.color.colorOnSecondary - ) - ) - it.setBackgroundColor( - ContextCompat.getColor( - application, R.color.colorSecondary - ) - ) - } - - buttons - .filterIndexed { i, _ -> i % 2 != 0 } - .forEach { - it.setTextColor( - ContextCompat.getColor( - application, R.color.colorOnPrimary - ) - ) - it.setBackgroundColor( - ContextCompat.getColor( - application, R.color.colorPrimary - ) - ) - } - } - - companion object { - private const val qrRequestCode = 0 - } -} +package com.github.braillesystems.learnbraille.ui.screens.menu + +import android.app.Activity.RESULT_OK +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.databinding.DataBindingUtil +import com.github.braillesystems.learnbraille.COURSE +import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase +import com.github.braillesystems.learnbraille.databinding.FragmentMenuBinding +import com.github.braillesystems.learnbraille.ui.screens.AbstractFragmentWithHelp +import com.github.braillesystems.learnbraille.ui.screens.theory.toLastCourseStep +import com.github.braillesystems.learnbraille.utils.* +import com.google.android.material.button.MaterialButton +import org.koin.android.ext.android.inject +import timber.log.Timber + +class MenuFragment : AbstractFragmentWithHelp(R.string.menu_help) { + + private val db: LearnBrailleDatabase by inject() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = DataBindingUtil.inflate( + inflater, + R.layout.fragment_menu, + container, + false + ).ini().also { binding -> + + title = getString(R.string.menu_actionbar_text_template).format(appName) + + val buttons = mutableListOf() + + binding.lessonsButton.also { + buttons += it + }.setOnClickListener(interruptingOnClickListener { + toLastCourseStep(COURSE.id) + }) + + binding.practiceButton.also { + buttons += it + }.setOnClickListener(interruptingOnClickListener { + navigate(R.id.action_menuFragment_to_practiceFragment) + }) + + binding.browserButton.also { + buttons += it + }.setOnClickListener(interruptingOnClickListener { + navigate(R.id.action_menuFragment_to_browserFragment) + }) + + binding.statsButton.also { + buttons += it + }.setOnClickListener(interruptingOnClickListener { + navigate(R.id.action_menuFragment_to_statsFragment) + }) + + if (preferenceRepository.additionalQrCodeButtonEnabled) { + binding.qrPracticeButton.also { + buttons += it + }.setOnClickListener { + try { + val intent = Intent("com.google.zxing.client.android.SCAN") + intent.putExtra("SCAN_MODE", "QR_CODE_MODE") + startActivityForResult(intent, qrRequestCode) + } catch (e: ActivityNotFoundException) { + checkedToast(getString(R.string.qr_intent_cancelled)) + sendMarketIntent("com.google.zxing.client.android") + } + } + } else { + binding.qrPracticeButton.visibility = View.GONE + } + + binding.settingsButton.also { + buttons += it + }.setOnClickListener { + navigate(R.id.action_menuFragment_to_settingsFragment) + } + + if (preferenceRepository.extendedAccessibilityEnabled) { + binding.exitButton.also { + buttons += it + }.setOnClickListener { + navigate(R.id.action_menuFragment_to_exitFragment) + } + } else { + binding.exitButton.visibility = View.GONE + } + + colorButtons(buttons) + + }.root + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + qrRequestCode -> processQrResult(resultCode, data) + } + } + + private fun processQrResult(resultCode: Int, data: Intent?) { + when (resultCode) { + RESULT_OK -> toast( + data?.getStringExtra("SCAN_RESULT") + ?: getString(R.string.menu_qr_empty_result).also { + Timber.e("QR: empty result with OK code") + } + ) + } + } + + private fun interruptingOnClickListener(block: (View) -> Unit) = + View.OnClickListener { + if (db.isInitialized) block(it) + else toast(getString(R.string.menu_db_not_initialized_warning)) + } + + private fun colorButtons(buttons: List) { + buttons + .filterIndexed { i, _ -> i % 2 == 0 } + .forEach { + it.setTextColor( + ContextCompat.getColor( + application, R.color.colorOnSecondary + ) + ) + it.setBackgroundColor( + ContextCompat.getColor( + application, R.color.colorSecondary + ) + ) + } + + buttons + .filterIndexed { i, _ -> i % 2 != 0 } + .forEach { + it.setTextColor( + ContextCompat.getColor( + application, R.color.colorOnPrimary + ) + ) + it.setBackgroundColor( + ContextCompat.getColor( + application, R.color.colorPrimary + ) + ) + } + } + + companion object { + private const val qrRequestCode = 0 + } +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardFragment.kt index 2ea31ff6..c457b372 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardFragment.kt @@ -1,192 +1,192 @@ -package com.github.braillesystems.learnbraille.ui.screens.practice - -import android.os.Bundle -import android.os.Vibrator -import android.view.* -import android.widget.Button -import android.widget.TextView -import androidx.core.content.getSystemService -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import com.github.braillesystems.learnbraille.R -import com.github.braillesystems.learnbraille.data.entities.MarkerSymbol -import com.github.braillesystems.learnbraille.data.entities.Symbol -import com.github.braillesystems.learnbraille.databinding.FragmentCardBinding -import com.github.braillesystems.learnbraille.res.captionRules -import com.github.braillesystems.learnbraille.res.deckTagToName -import com.github.braillesystems.learnbraille.res.inputMarkerPrintRules -import com.github.braillesystems.learnbraille.ui.* -import com.github.braillesystems.learnbraille.ui.brailletrainer.BrailleTrainer -import com.github.braillesystems.learnbraille.ui.brailletrainer.BrailleTrainerSignalHandler -import com.github.braillesystems.learnbraille.ui.screens.* -import com.github.braillesystems.learnbraille.ui.views.* -import com.github.braillesystems.learnbraille.utils.* -import org.koin.android.ext.android.inject -import org.koin.core.parameter.parametersOf -import timber.log.Timber - -class CardFragment : AbstractFragmentWithHelp(R.string.practice_help) { - - // This value can change during ViewModel lifetime (ViewModelProvider does not call - // ViewModelFactory each time onCreateView runs). And once created ViewModel - // should be able to use up to date dotsState. - private lateinit var dotsState: BrailleDotsState - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DataBindingUtil.inflate( - inflater, - R.layout.fragment_card, - container, - false - ).ini { - object : FragmentBinding { - override val leftButton: Button? = this@ini.hintButton - override val rightButton: Button? = this@ini.nextButton - override val rightMiddleButton: Button? = this@ini.flipButton - override val textView: TextView? = this@ini.markerDescription - override val brailleDotsInfo: BrailleDotsInfo? = this@ini.run { - BrailleDotsInfo( - brailleDots, - if (preferenceRepository.isWriteModeFirst) BrailleDotsViewMode.Writing - else BrailleDotsViewMode.Reading, - hintButton, flipButton - ) - } - } - }.also { binding -> - - Timber.i("onCreateView") - - title = title() - - dotsState = binding.brailleDots.dotsState - - val viewModelFactory: CardViewModelFactory by inject { - parametersOf({ dotsState.brailleDots }) - } - val viewModel = ViewModelProvider(this, viewModelFactory) - .get(CardViewModel::class.java) - - dotsState.subscribe(viewModel) - - val buzzer: Vibrator? = activity?.getSystemService() - - BrailleTrainer.setSignalHandler(object : BrailleTrainerSignalHandler { - override fun onJoystickRight() = viewModel.onCheck() - override fun onJoystickLeft() = viewModel.onHint() - }) - - binding.cardViewModel = viewModel - binding.lifecycleOwner = this@CardFragment - - binding.flipButton.setOnClickListener { - dotsState = binding.brailleDots.reflect().apply { - dotsState.subscribe(viewModel) - if (viewModel.state == DotsChecker.State.HINT) { - viewModel.expectedDots?.let { display(it) } - } - } - } - - viewModel.symbol.observe(viewLifecycleOwner, Observer { - if (it == null) return@Observer - when (it) { - is Symbol -> { - binding.letter.visibility = View.VISIBLE - binding.markerDescription.visibility = View.GONE - binding.letter.letter = it.char - binding.letterCaption.text = captionRules.getValue(it) - } - is MarkerSymbol -> { - binding.letter.visibility = View.GONE - binding.markerDescription.visibility = View.VISIBLE - binding.markerDescription.text = inputMarkerPrintRules[it.type] - binding.letterCaption.text = "" - } - } - }) - - viewModel.observeCheckedOnFly( - viewLifecycleOwner, { dotsState }, buzzer, - block = { title = title(viewModel) }, - softBlock = ::showCorrectToast - ) - - viewModel.observeEventIncorrect( - viewLifecycleOwner, { dotsState }, buzzer - ) { - viewModel.symbol.value - ?.let { showIncorrectToast(inputPrint(it)) } - ?: checkedToast(getString(R.string.input_loading)) - title = title(viewModel) - } - - viewModel.observeEventHint( - viewLifecycleOwner, { dotsState } - ) { expectedDots -> - showHintToast(expectedDots) - } - - viewModel.observeEventPassHint( - viewLifecycleOwner, { dotsState } - ) { - viewModel.symbol.value?.let { - checkedAnnounce(inputPrint(it)) - } - } - - viewModel.symbol.observe( - viewLifecycleOwner, - Observer { - if (it == null) return@Observer - checkedAnnounce(inputPrint(it)) - } - ) - - viewModel.deckTag.observe( - viewLifecycleOwner, - Observer { tag -> - if (tag == null) return@Observer - val template = if (preferenceRepository.practiceUseOnlyKnownMaterials) { - getString(R.string.practice_deck_name_enabled_template) - } else { - getString(R.string.practice_deck_name_disabled_template) - } - toast( - template.format(deckTagToName.getValue(tag)) - ) - } - ) - - if (viewModel.state == DotsChecker.State.HINT) { - viewModel.expectedDots?.let { dotsState.display(it) } - } - - }.root - - private fun title(viewModel: CardViewModel? = null): String = - getString(R.string.practice_actionbar_title_template).run { - if (viewModel == null) format(0, 0) - else format(viewModel.nCorrect, viewModel.nTries) - } - - private fun BrailleDotsState.subscribe(viewModel: CardViewModel) = - subscribe(View.OnClickListener { - viewModel.onSoftCheck() - }) - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.card_menu, menu) - } - - override fun onOptionsItemSelected(item: MenuItem) = false.also { - when (item.itemId) { - R.id.help -> navigateToHelp() - R.id.decks_list -> navigate(R.id.action_cardFragment_to_decksList) - } - } -} +package com.github.braillesystems.learnbraille.ui.screens.practice + +import android.os.Bundle +import android.os.Vibrator +import android.view.* +import android.widget.Button +import android.widget.TextView +import androidx.core.content.getSystemService +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.data.entities.MarkerSymbol +import com.github.braillesystems.learnbraille.data.entities.Symbol +import com.github.braillesystems.learnbraille.databinding.FragmentCardBinding +import com.github.braillesystems.learnbraille.res.captionRules +import com.github.braillesystems.learnbraille.res.deckTagToName +import com.github.braillesystems.learnbraille.res.inputMarkerPrintRules +import com.github.braillesystems.learnbraille.ui.* +import com.github.braillesystems.learnbraille.ui.brailletrainer.BrailleTrainer +import com.github.braillesystems.learnbraille.ui.brailletrainer.BrailleTrainerSignalHandler +import com.github.braillesystems.learnbraille.ui.screens.* +import com.github.braillesystems.learnbraille.ui.views.* +import com.github.braillesystems.learnbraille.utils.* +import org.koin.android.ext.android.inject +import org.koin.core.parameter.parametersOf +import timber.log.Timber + +class CardFragment : AbstractFragmentWithHelp(R.string.practice_help) { + + // This value can change during ViewModel lifetime (ViewModelProvider does not call + // ViewModelFactory each time onCreateView runs). And once created ViewModel + // should be able to use up to date dotsState. + private lateinit var dotsState: BrailleDotsState + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = DataBindingUtil.inflate( + inflater, + R.layout.fragment_card, + container, + false + ).ini { + object : FragmentBinding { + override val leftButton: Button? = this@ini.hintButton + override val rightButton: Button? = this@ini.nextButton + override val rightMiddleButton: Button? = this@ini.flipButton + override val textView: TextView? = this@ini.markerDescription + override val brailleDotsInfo: BrailleDotsInfo? = this@ini.run { + BrailleDotsInfo( + brailleDots, + if (preferenceRepository.isWriteModeFirst) BrailleDotsViewMode.Writing + else BrailleDotsViewMode.Reading, + hintButton, flipButton + ) + } + } + }.also { binding -> + + Timber.i("onCreateView") + + title = title() + + dotsState = binding.brailleDots.dotsState + + val viewModelFactory: CardViewModelFactory by inject { + parametersOf({ dotsState.brailleDots }) + } + val viewModel = ViewModelProvider(this, viewModelFactory) + .get(CardViewModel::class.java) + + dotsState.subscribe(viewModel) + + val buzzer: Vibrator? = activity?.getSystemService() + + BrailleTrainer.setSignalHandler(object : BrailleTrainerSignalHandler { + override fun onJoystickRight() = viewModel.onCheck() + override fun onJoystickLeft() = viewModel.onHint() + }) + + binding.cardViewModel = viewModel + binding.lifecycleOwner = this@CardFragment + + binding.flipButton.setOnClickListener { + dotsState = binding.brailleDots.reflect().apply { + dotsState.subscribe(viewModel) + if (viewModel.state == DotsChecker.State.HINT) { + viewModel.expectedDots?.let { display(it) } + } + } + } + + viewModel.symbol.observe(viewLifecycleOwner, Observer { + if (it == null) return@Observer + when (it) { + is Symbol -> { + binding.letter.visibility = View.VISIBLE + binding.markerDescription.visibility = View.GONE + binding.letter.letter = it.char + binding.letterCaption.text = captionRules.getValue(it) + } + is MarkerSymbol -> { + binding.letter.visibility = View.GONE + binding.markerDescription.visibility = View.VISIBLE + binding.markerDescription.text = inputMarkerPrintRules[it.type] + binding.letterCaption.text = "" + } + } + }) + + viewModel.observeCheckedOnFly( + viewLifecycleOwner, { dotsState }, buzzer, + block = { title = title(viewModel) }, + softBlock = ::showCorrectToast + ) + + viewModel.observeEventIncorrect( + viewLifecycleOwner, { dotsState }, buzzer + ) { + viewModel.symbol.value + ?.let { showIncorrectToast(inputPrint(it)) } + ?: checkedToast(getString(R.string.input_loading)) + title = title(viewModel) + } + + viewModel.observeEventHint( + viewLifecycleOwner, { dotsState } + ) { expectedDots -> + showHintToast(expectedDots) + } + + viewModel.observeEventPassHint( + viewLifecycleOwner, { dotsState } + ) { + viewModel.symbol.value?.let { + checkedAnnounce(inputPrint(it)) + } + } + + viewModel.symbol.observe( + viewLifecycleOwner, + Observer { + if (it == null) return@Observer + checkedAnnounce(inputPrint(it)) + } + ) + + viewModel.deckTag.observe( + viewLifecycleOwner, + Observer { tag -> + if (tag == null) return@Observer + val template = if (preferenceRepository.practiceUseOnlyKnownMaterials) { + getString(R.string.practice_deck_name_enabled_template) + } else { + getString(R.string.practice_deck_name_disabled_template) + } + toast( + template.format(deckTagToName.getValue(tag)) + ) + } + ) + + if (viewModel.state == DotsChecker.State.HINT) { + viewModel.expectedDots?.let { dotsState.display(it) } + } + + }.root + + private fun title(viewModel: CardViewModel? = null): String = + getString(R.string.practice_actionbar_title_template).run { + if (viewModel == null) format(0, 0) + else format(viewModel.nCorrect, viewModel.nTries) + } + + private fun BrailleDotsState.subscribe(viewModel: CardViewModel) = + subscribe(View.OnClickListener { + viewModel.onSoftCheck() + }) + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.card_menu, menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = false.also { + when (item.itemId) { + R.id.help -> navigateToHelp() + R.id.decks_list -> navigate(R.id.action_cardFragment_to_decksList) + } + } +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardViewModel.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardViewModel.kt index acb85db0..6fe66e3d 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardViewModel.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/practice/CardViewModel.kt @@ -1,150 +1,150 @@ -package com.github.braillesystems.learnbraille.ui.screens.practice - -import android.app.Application -import androidx.lifecycle.* -import com.github.braillesystems.learnbraille.data.dsl.ALL_CARDS_DECK_ID -import com.github.braillesystems.learnbraille.data.entities.* -import com.github.braillesystems.learnbraille.data.repository.MutableActionsRepository -import com.github.braillesystems.learnbraille.data.repository.MutablePracticeRepository -import com.github.braillesystems.learnbraille.data.repository.PreferenceRepository -import com.github.braillesystems.learnbraille.ui.screens.DotsChecker -import com.github.braillesystems.learnbraille.ui.screens.MutableDotsChecker -import com.github.braillesystems.learnbraille.utils.retryN -import com.github.braillesystems.learnbraille.utils.scope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import timber.log.Timber -import java.util.* -import java.util.concurrent.ArrayBlockingQueue - -class CardViewModelFactory( - private val practiceRepository: MutablePracticeRepository, - private val actionsRepository: MutableActionsRepository, - private val preferenceRepository: PreferenceRepository, - private val application: Application, - private val getEnteredDots: () -> BrailleDots -) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T = - if (modelClass.isAssignableFrom(CardViewModel::class.java)) { - @Suppress("UNCHECKED_CAST") - CardViewModel( - practiceRepository, - actionsRepository, - preferenceRepository, - application, - getEnteredDots - ) as T - } else { - throw IllegalArgumentException("Unknown ViewModel class") - } -} - -class CardViewModel( - private val practiceRepository: MutablePracticeRepository, - private val actionsRepository: MutableActionsRepository, - private val preferenceRepository: PreferenceRepository, - application: Application, - private val getEnteredDots: () -> BrailleDots, - private val dotsChecker: MutableDotsChecker = MutableDotsChecker.create() -) : AndroidViewModel(application), - DotsChecker by dotsChecker { - - private val _symbol = MutableLiveData() - val symbol: LiveData get() = _symbol - - private val _deckTag = MutableLiveData() - val deckTag: LiveData get() = _deckTag - - var nTries: Int = 0 - private set - - var nCorrect: Int = 0 - private set - - var expectedDots: BrailleDots? = null - private set - - private val job = Job() - private val uiScope = scope(job) - - private val nSkipMaterials = 2 - private val materialsQueue: Queue = ArrayBlockingQueue(nSkipMaterials) - - init { - Timber.i("Initialize practice view model") - initializeCard(firstTime = true) - - dotsChecker.apply { - getEnteredDots = this@CardViewModel.getEnteredDots - getExpectedDots = { expectedDots } - onCheckHandler = { - if (dotsChecker.state == DotsChecker.State.INPUT) { - nTries++ - } - } - onCorrectHandler = { - initializeCard() - nCorrect++ - uiScope.launch { - actionsRepository.addAction(PracticeSubmission(isCorrect = true)) - } - } - onHintHandler = { - uiScope.launch { - actionsRepository.addAction(PracticeHintAction) - } - } - onIncorrectHandler = { - uiScope.launch { - actionsRepository.addAction(PracticeSubmission(isCorrect = false)) - } - } - } - } - - override fun onCleared() { - super.onCleared() - job.cancel() - } - - private fun initializeCard(firstTime: Boolean = false) = uiScope.launch { - // If `use only known materials` turned on and current deck became unavailable - if (preferenceRepository.practiceUseOnlyKnownMaterials && - practiceRepository.randomKnownMaterial() == null - ) { - practiceRepository.currentDeckId = ALL_CARDS_DECK_ID - } - - if (firstTime) { - val deck = practiceRepository.currentDeck() - _deckTag.value = deck.tag - } - - val material = retryN(5) { - val m = nextMaterial() - if (m.data in materialsQueue) null - else m - } ?: nextMaterial() - - if (material.data !in materialsQueue) { - if (materialsQueue.size == nSkipMaterials) materialsQueue.poll() - materialsQueue.add(material.data) - } - - material.data.let { - _symbol.value = it - expectedDots = when (it) { - is OneBrailleSymbol -> it.brailleDots - } - } - } - - private suspend fun nextMaterial(): Material = - if (preferenceRepository.practiceUseOnlyKnownMaterials) { - practiceRepository.randomKnownMaterial() - } else { - practiceRepository.randomMaterial() - } - ?: error("Current deck should not be empty") -} +package com.github.braillesystems.learnbraille.ui.screens.practice + +import android.app.Application +import androidx.lifecycle.* +import com.github.braillesystems.learnbraille.data.dsl.ALL_CARDS_DECK_ID +import com.github.braillesystems.learnbraille.data.entities.* +import com.github.braillesystems.learnbraille.data.repository.MutableActionsRepository +import com.github.braillesystems.learnbraille.data.repository.MutablePracticeRepository +import com.github.braillesystems.learnbraille.data.repository.PreferenceRepository +import com.github.braillesystems.learnbraille.ui.screens.DotsChecker +import com.github.braillesystems.learnbraille.ui.screens.MutableDotsChecker +import com.github.braillesystems.learnbraille.utils.retryN +import com.github.braillesystems.learnbraille.utils.scope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import timber.log.Timber +import java.util.* +import java.util.concurrent.ArrayBlockingQueue + +class CardViewModelFactory( + private val practiceRepository: MutablePracticeRepository, + private val actionsRepository: MutableActionsRepository, + private val preferenceRepository: PreferenceRepository, + private val application: Application, + private val getEnteredDots: () -> BrailleDots +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T = + if (modelClass.isAssignableFrom(CardViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + CardViewModel( + practiceRepository, + actionsRepository, + preferenceRepository, + application, + getEnteredDots + ) as T + } else { + throw IllegalArgumentException("Unknown ViewModel class") + } +} + +class CardViewModel( + private val practiceRepository: MutablePracticeRepository, + private val actionsRepository: MutableActionsRepository, + private val preferenceRepository: PreferenceRepository, + application: Application, + private val getEnteredDots: () -> BrailleDots, + private val dotsChecker: MutableDotsChecker = MutableDotsChecker.create() +) : AndroidViewModel(application), + DotsChecker by dotsChecker { + + private val _symbol = MutableLiveData() + val symbol: LiveData get() = _symbol + + private val _deckTag = MutableLiveData() + val deckTag: LiveData get() = _deckTag + + var nTries: Int = 0 + private set + + var nCorrect: Int = 0 + private set + + var expectedDots: BrailleDots? = null + private set + + private val job = Job() + private val uiScope = scope(job) + + private val nSkipMaterials = 2 + private val materialsQueue: Queue = ArrayBlockingQueue(nSkipMaterials) + + init { + Timber.i("Initialize practice view model") + initializeCard(firstTime = true) + + dotsChecker.apply { + getEnteredDots = this@CardViewModel.getEnteredDots + getExpectedDots = { expectedDots } + onCheckHandler = { + if (dotsChecker.state == DotsChecker.State.INPUT) { + nTries++ + } + } + onCorrectHandler = { + initializeCard() + nCorrect++ + uiScope.launch { + actionsRepository.addAction(PracticeSubmission(isCorrect = true)) + } + } + onHintHandler = { + uiScope.launch { + actionsRepository.addAction(PracticeHintAction) + } + } + onIncorrectHandler = { + uiScope.launch { + actionsRepository.addAction(PracticeSubmission(isCorrect = false)) + } + } + } + } + + override fun onCleared() { + super.onCleared() + job.cancel() + } + + private fun initializeCard(firstTime: Boolean = false) = uiScope.launch { + // If `use only known materials` turned on and current deck became unavailable + if (preferenceRepository.practiceUseOnlyKnownMaterials && + practiceRepository.randomKnownMaterial() == null + ) { + practiceRepository.currentDeckId = ALL_CARDS_DECK_ID + } + + if (firstTime) { + val deck = practiceRepository.currentDeck() + _deckTag.value = deck.tag + } + + val material = retryN(5) { + val m = nextMaterial() + if (m.data in materialsQueue) null + else m + } ?: nextMaterial() + + if (material.data !in materialsQueue) { + if (materialsQueue.size == nSkipMaterials) materialsQueue.poll() + materialsQueue.add(material.data) + } + + material.data.let { + _symbol.value = it + expectedDots = when (it) { + is OneBrailleSymbol -> it.brailleDots + } + } + } + + private suspend fun nextMaterial(): Material = + if (preferenceRepository.practiceUseOnlyKnownMaterials) { + practiceRepository.randomKnownMaterial() + } else { + practiceRepository.randomMaterial() + } + ?: error("Current deck should not be empty") +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt index c9a45232..a109a759 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt @@ -1,11 +1,11 @@ -package com.github.braillesystems.learnbraille.ui.screens.settings - -import android.os.Bundle -import com.github.braillesystems.learnbraille.R - -class SettingsFragment : androidx.preference.PreferenceFragmentCompat() { - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.settings_hierarchy, rootKey) - } -} +package com.github.braillesystems.learnbraille.ui.screens.settings + +import android.os.Bundle +import com.github.braillesystems.learnbraille.R + +class SettingsFragment : androidx.preference.PreferenceFragmentCompat() { + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.settings_hierarchy, rootKey) + } +} diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/views/BrailleDotsView.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/views/BrailleDotsView.kt index d4551ec8..7708f24e 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/views/BrailleDotsView.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/views/BrailleDotsView.kt @@ -1,232 +1,232 @@ -package com.github.braillesystems.learnbraille.ui.views - -import android.annotation.SuppressLint -import android.content.Context -import android.os.Build -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.accessibility.AccessibilityNodeInfo -import android.widget.CheckBox -import androidx.annotation.RequiresApi -import androidx.constraintlayout.widget.ConstraintLayout -import com.github.braillesystems.learnbraille.R -import com.github.braillesystems.learnbraille.data.entities.BrailleDot -import com.github.braillesystems.learnbraille.data.entities.BrailleDots -import com.github.braillesystems.learnbraille.data.entities.list -import com.github.braillesystems.learnbraille.data.entities.spelling -import com.github.braillesystems.learnbraille.data.repository.PreferenceRepository -import com.github.braillesystems.learnbraille.ui.views.BrailleDotsViewMode.Reading -import com.github.braillesystems.learnbraille.ui.views.BrailleDotsViewMode.Writing -import com.github.braillesystems.learnbraille.utils.* -import kotlinx.android.synthetic.main.braille_dots_view.view.* -import org.koin.core.KoinComponent -import org.koin.core.inject -import timber.log.Timber - -@SuppressLint("AppCompatCustomView") // Causes BrailleDotView misplacement -class BrailleDotView : CheckBox { - - constructor(context: Context) : super(context) - - constructor(context: Context, attrSet: AttributeSet) : super(context, attrSet) - - constructor( - context: Context, attrSet: AttributeSet, defStyleAttr: Int - ) : super( - context, attrSet, defStyleAttr - ) - - override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) { - super.onInitializeAccessibilityNodeInfo(info) - info?.className = "" - } -} - -enum class BrailleDotsViewMode { - Reading, // 1, 2, 3 dots are on the LEFT - Writing // 1, 2, 3 dots are on the RIGHT -} - -val BrailleDotsViewMode.reflected: BrailleDotsViewMode - get() = when (this) { - Writing -> Reading - Reading -> Writing - } - -/** - * Represents six Braille dots view. - * - * Initialize by `setMode` before usage. - */ -class BrailleDotsView : ConstraintLayout, KoinComponent { - - private val preferenceRepository: PreferenceRepository by inject() - - constructor(context: Context) : super(context) - - constructor(context: Context, attrSet: AttributeSet) : super(context, attrSet) - - constructor( - context: Context, attrSet: AttributeSet, defStyleAttr: Int - ) : super( - context, attrSet, defStyleAttr - ) - - init { - LayoutInflater - .from(context) - .inflate(R.layout.braille_dots_view, this, true) - } - - // After changing traversal order neighbor views forget that braille dots are next - private lateinit var prevView: View - private lateinit var nextView: View - lateinit var mode: BrailleDotsViewMode - private set // It is not possible to use lateinit var with custom setter - - fun setMode(mode: BrailleDotsViewMode, prevView: View, nextView: View) { - this.prevView = prevView - this.nextView = nextView - - setDescriptionMode(mode) - if (this::mode.isInitialized && this.mode != mode) { - reflectChecks() - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - setBackgroundMode(mode) - } else { - Timber.w("Unable to set braille dots background due to low API level") - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - setTraversalMode(mode) - } else { - Timber.w("API level < 22, unable co control accessibility traversal order") - } - - context.announce( - when (mode) { - Writing -> context.getString(R.string.braille_dots_mode_writing) - Reading -> context.getString(R.string.braille_dots_mode_reading) - } - ) - - this.mode = mode - } - - fun reflect(): BrailleDotsState { - setMode(mode.reflected, prevView, nextView) - return dotsState - } - - private fun setDescriptionMode(mode: BrailleDotsViewMode) { - val dotsMapping = when (mode) { - Writing -> listOf( - Triple(dotButton4, R.string.braille_dot_1, R.string.braille_dot_1_text), - Triple(dotButton5, R.string.braille_dot_2, R.string.braille_dot_2_text), - Triple(dotButton6, R.string.braille_dot_3, R.string.braille_dot_3_text), - Triple(dotButton1, R.string.braille_dot_4, R.string.braille_dot_4_text), - Triple(dotButton2, R.string.braille_dot_5, R.string.braille_dot_5_text), - Triple(dotButton3, R.string.braille_dot_6, R.string.braille_dot_6_text) - ) - Reading -> listOf( - Triple(dotButton1, R.string.braille_dot_1, R.string.braille_dot_1_text), - Triple(dotButton2, R.string.braille_dot_2, R.string.braille_dot_2_text), - Triple(dotButton3, R.string.braille_dot_3, R.string.braille_dot_3_text), - Triple(dotButton4, R.string.braille_dot_4, R.string.braille_dot_4_text), - Triple(dotButton5, R.string.braille_dot_5, R.string.braille_dot_5_text), - Triple(dotButton6, R.string.braille_dot_6, R.string.braille_dot_6_text) - ) - } - dotsMapping.forEach { (dotButton, desc_id, caption_id) -> - dotButton.contentDescription = context.getString(desc_id) - dotButton.text = context.getString(caption_id) - } - } - - private fun reflectChecks() = forEach( - dotButton1 to dotButton4, - dotButton2 to dotButton5, - dotButton3 to dotButton6 - ) { (left, right) -> - left.isChecked = right.isChecked.also { - right.isChecked = left.isChecked - } - } - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) - private fun setTraversalMode(mode: BrailleDotsViewMode) { - val dotsOrder: Array = - when (mode to preferenceRepository.traverseDotsInEnumerationOrder) { - Writing to true -> arrayOf( - dotButton1, dotButton2, dotButton3, - dotButton4, dotButton5, dotButton6 - ) - Writing to false -> arrayOf( - dotButton1, dotButton4, dotButton2, - dotButton5, dotButton3, dotButton6 - ) - Reading to true -> arrayOf( - dotButton1, dotButton2, dotButton3, - dotButton4, dotButton5, dotButton6 - ) - Reading to false -> arrayOf( - dotButton1, dotButton4, dotButton2, - dotButton5, dotButton3, dotButton6 - ) - else -> unreachable - } - @Suppress("SpreadOperator") - chainify(prevView, *dotsOrder, nextView) { prev, next -> - prev.accessibilityTraversalBefore = next.id - next.accessibilityTraversalAfter = prev.id - } - } - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - private fun setBackgroundMode(mode: BrailleDotsViewMode) { - background = when (mode) { - Writing -> context.getDrawable(R.drawable.right_border) - Reading -> context.getDrawable(R.drawable.left_border) - } - } -} - -val BrailleDotsView.dotsState: BrailleDotsState - get() = BrailleDotsState( - when (mode) { - Writing -> listOf( - dotButton4, dotButton5, dotButton6, - dotButton1, dotButton2, dotButton3 - ) - Reading -> listOf( - dotButton1, dotButton2, dotButton3, - dotButton4, dotButton5, dotButton6 - ) - } - ) - -class BrailleDotsState(val checkBoxes: List) - -val BrailleDotsState.spelling: String - get() = brailleDots.spelling - -val BrailleDotsState.brailleDots: BrailleDots - get() = BrailleDots( - checkBoxes.map(CheckBox::isChecked).toBooleanArray() - ) - -fun BrailleDotsState.uncheck() = checkBoxes.forEach { it.isChecked = false } - -fun BrailleDotsState.clickable(isClickable: Boolean) = - checkBoxes.forEach { it.isClickable = isClickable } - -fun BrailleDotsState.subscribe(listener: View.OnClickListener) = - checkBoxes.forEach { it.setOnClickListener(listener) } - -fun BrailleDotsState.display(brailleDots: BrailleDots): Unit = - (checkBoxes zip brailleDots.list) - .forEach { (checkBox, dot) -> checkBox.isChecked = dot == BrailleDot.F } - .also { clickable(false) } +package com.github.braillesystems.learnbraille.ui.views + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.CheckBox +import androidx.annotation.RequiresApi +import androidx.constraintlayout.widget.ConstraintLayout +import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.data.entities.BrailleDot +import com.github.braillesystems.learnbraille.data.entities.BrailleDots +import com.github.braillesystems.learnbraille.data.entities.list +import com.github.braillesystems.learnbraille.data.entities.spelling +import com.github.braillesystems.learnbraille.data.repository.PreferenceRepository +import com.github.braillesystems.learnbraille.ui.views.BrailleDotsViewMode.Reading +import com.github.braillesystems.learnbraille.ui.views.BrailleDotsViewMode.Writing +import com.github.braillesystems.learnbraille.utils.* +import kotlinx.android.synthetic.main.braille_dots_view.view.* +import org.koin.core.KoinComponent +import org.koin.core.inject +import timber.log.Timber + +@SuppressLint("AppCompatCustomView") // Causes BrailleDotView misplacement +class BrailleDotView : CheckBox { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrSet: AttributeSet) : super(context, attrSet) + + constructor( + context: Context, attrSet: AttributeSet, defStyleAttr: Int + ) : super( + context, attrSet, defStyleAttr + ) + + override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) { + super.onInitializeAccessibilityNodeInfo(info) + info?.className = "" + } +} + +enum class BrailleDotsViewMode { + Reading, // 1, 2, 3 dots are on the LEFT + Writing // 1, 2, 3 dots are on the RIGHT +} + +val BrailleDotsViewMode.reflected: BrailleDotsViewMode + get() = when (this) { + Writing -> Reading + Reading -> Writing + } + +/** + * Represents six Braille dots view. + * + * Initialize by `setMode` before usage. + */ +class BrailleDotsView : ConstraintLayout, KoinComponent { + + private val preferenceRepository: PreferenceRepository by inject() + + constructor(context: Context) : super(context) + + constructor(context: Context, attrSet: AttributeSet) : super(context, attrSet) + + constructor( + context: Context, attrSet: AttributeSet, defStyleAttr: Int + ) : super( + context, attrSet, defStyleAttr + ) + + init { + LayoutInflater + .from(context) + .inflate(R.layout.braille_dots_view, this, true) + } + + // After changing traversal order neighbor views forget that braille dots are next + private lateinit var prevView: View + private lateinit var nextView: View + lateinit var mode: BrailleDotsViewMode + private set // It is not possible to use lateinit var with custom setter + + fun setMode(mode: BrailleDotsViewMode, prevView: View, nextView: View) { + this.prevView = prevView + this.nextView = nextView + + setDescriptionMode(mode) + if (this::mode.isInitialized && this.mode != mode) { + reflectChecks() + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + setBackgroundMode(mode) + } else { + Timber.w("Unable to set braille dots background due to low API level") + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + setTraversalMode(mode) + } else { + Timber.w("API level < 22, unable co control accessibility traversal order") + } + + context.announce( + when (mode) { + Writing -> context.getString(R.string.braille_dots_mode_writing) + Reading -> context.getString(R.string.braille_dots_mode_reading) + } + ) + + this.mode = mode + } + + fun reflect(): BrailleDotsState { + setMode(mode.reflected, prevView, nextView) + return dotsState + } + + private fun setDescriptionMode(mode: BrailleDotsViewMode) { + val dotsMapping = when (mode) { + Writing -> listOf( + Triple(dotButton4, R.string.braille_dot_1, R.string.braille_dot_1_text), + Triple(dotButton5, R.string.braille_dot_2, R.string.braille_dot_2_text), + Triple(dotButton6, R.string.braille_dot_3, R.string.braille_dot_3_text), + Triple(dotButton1, R.string.braille_dot_4, R.string.braille_dot_4_text), + Triple(dotButton2, R.string.braille_dot_5, R.string.braille_dot_5_text), + Triple(dotButton3, R.string.braille_dot_6, R.string.braille_dot_6_text) + ) + Reading -> listOf( + Triple(dotButton1, R.string.braille_dot_1, R.string.braille_dot_1_text), + Triple(dotButton2, R.string.braille_dot_2, R.string.braille_dot_2_text), + Triple(dotButton3, R.string.braille_dot_3, R.string.braille_dot_3_text), + Triple(dotButton4, R.string.braille_dot_4, R.string.braille_dot_4_text), + Triple(dotButton5, R.string.braille_dot_5, R.string.braille_dot_5_text), + Triple(dotButton6, R.string.braille_dot_6, R.string.braille_dot_6_text) + ) + } + dotsMapping.forEach { (dotButton, desc_id, caption_id) -> + dotButton.contentDescription = context.getString(desc_id) + dotButton.text = context.getString(caption_id) + } + } + + private fun reflectChecks() = forEach( + dotButton1 to dotButton4, + dotButton2 to dotButton5, + dotButton3 to dotButton6 + ) { (left, right) -> + left.isChecked = right.isChecked.also { + right.isChecked = left.isChecked + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) + private fun setTraversalMode(mode: BrailleDotsViewMode) { + val dotsOrder: Array = + when (mode to preferenceRepository.traverseDotsInEnumerationOrder) { + Writing to true -> arrayOf( + dotButton1, dotButton2, dotButton3, + dotButton4, dotButton5, dotButton6 + ) + Writing to false -> arrayOf( + dotButton1, dotButton4, dotButton2, + dotButton5, dotButton3, dotButton6 + ) + Reading to true -> arrayOf( + dotButton1, dotButton2, dotButton3, + dotButton4, dotButton5, dotButton6 + ) + Reading to false -> arrayOf( + dotButton1, dotButton4, dotButton2, + dotButton5, dotButton3, dotButton6 + ) + else -> unreachable + } + @Suppress("SpreadOperator") + chainify(prevView, *dotsOrder, nextView) { prev, next -> + prev.accessibilityTraversalBefore = next.id + next.accessibilityTraversalAfter = prev.id + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun setBackgroundMode(mode: BrailleDotsViewMode) { + background = when (mode) { + Writing -> context.getDrawable(R.drawable.right_border) + Reading -> context.getDrawable(R.drawable.left_border) + } + } +} + +val BrailleDotsView.dotsState: BrailleDotsState + get() = BrailleDotsState( + when (mode) { + Writing -> listOf( + dotButton4, dotButton5, dotButton6, + dotButton1, dotButton2, dotButton3 + ) + Reading -> listOf( + dotButton1, dotButton2, dotButton3, + dotButton4, dotButton5, dotButton6 + ) + } + ) + +class BrailleDotsState(val checkBoxes: List) + +val BrailleDotsState.spelling: String + get() = brailleDots.spelling + +val BrailleDotsState.brailleDots: BrailleDots + get() = BrailleDots( + checkBoxes.map(CheckBox::isChecked).toBooleanArray() + ) + +fun BrailleDotsState.uncheck() = checkBoxes.forEach { it.isChecked = false } + +fun BrailleDotsState.clickable(isClickable: Boolean) = + checkBoxes.forEach { it.isClickable = isClickable } + +fun BrailleDotsState.subscribe(listener: View.OnClickListener) = + checkBoxes.forEach { it.setOnClickListener(listener) } + +fun BrailleDotsState.display(brailleDots: BrailleDots): Unit = + (checkBoxes zip brailleDots.list) + .forEach { (checkBox, dot) -> checkBox.isChecked = dot == BrailleDot.F } + .also { clickable(false) } diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index 971add5e..1f6bb290 100644 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,34 +1,34 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/action_menu_help_button.xml b/app/src/main/res/drawable/action_menu_help_button.xml index 7ab092bb..4ba5e2f5 100644 --- a/app/src/main/res/drawable/action_menu_help_button.xml +++ b/app/src/main/res/drawable/action_menu_help_button.xml @@ -1,9 +1,9 @@ - - - + + + diff --git a/app/src/main/res/drawable/checked_round_checkbox.xml b/app/src/main/res/drawable/checked_round_checkbox.xml index 281e0730..be2c1947 100644 --- a/app/src/main/res/drawable/checked_round_checkbox.xml +++ b/app/src/main/res/drawable/checked_round_checkbox.xml @@ -1,8 +1,8 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index eed7a425..0d025f9b 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,170 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/round_checkbox.xml b/app/src/main/res/drawable/round_checkbox.xml index e67a3adc..416a34e6 100644 --- a/app/src/main/res/drawable/round_checkbox.xml +++ b/app/src/main/res/drawable/round_checkbox.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/unchecked_round_checkbox.xml b/app/src/main/res/drawable/unchecked_round_checkbox.xml index 5909773d..5ffd335b 100644 --- a/app/src/main/res/drawable/unchecked_round_checkbox.xml +++ b/app/src/main/res/drawable/unchecked_round_checkbox.xml @@ -1,10 +1,10 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c4ff5fa0..13c0c1a9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,17 +1,17 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/braille_dots_view.xml b/app/src/main/res/layout/braille_dots_view.xml index b17e598b..16d0cef5 100644 --- a/app/src/main/res/layout/braille_dots_view.xml +++ b/app/src/main/res/layout/braille_dots_view.xml @@ -1,104 +1,104 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_card.xml b/app/src/main/res/layout/fragment_card.xml index f5af9814..7449e68f 100644 --- a/app/src/main/res/layout/fragment_card.xml +++ b/app/src/main/res/layout/fragment_card.xml @@ -1,109 +1,109 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_exit.xml b/app/src/main/res/layout/fragment_exit.xml index 693849d3..12379a59 100644 --- a/app/src/main/res/layout/fragment_exit.xml +++ b/app/src/main/res/layout/fragment_exit.xml @@ -1,35 +1,35 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_help.xml b/app/src/main/res/layout/fragment_help.xml index 1f1369d7..e2710ff3 100644 --- a/app/src/main/res/layout/fragment_help.xml +++ b/app/src/main/res/layout/fragment_help.xml @@ -1,15 +1,15 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_menu.xml b/app/src/main/res/layout/fragment_menu.xml index d3382c9f..e102301d 100644 --- a/app/src/main/res/layout/fragment_menu.xml +++ b/app/src/main/res/layout/fragment_menu.xml @@ -1,108 +1,108 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 56e36c84..be316184 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 56e36c84..be316184 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml index 901cfac5..60a7fa1c 100644 --- a/app/src/main/res/navigation/navigation.xml +++ b/app/src/main/res/navigation/navigation.xml @@ -1,248 +1,248 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index 90791389..0929e3ee 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -1,37 +1,37 @@ - - - 25dp - 3dp - 30dp - 30dp - 20dp - 25dp - 2.6 - 1.8 - - 1dp - - 80dp - 65dp - 300dp - 150dp - 70dp - 60dp - 55dp - - 35sp - 0.05 - 450dp - 0dp - 30sp - 0.0 - 394dp - 175dp - - 285sp - - 30dp - - 45dp - + + + 25dp + 3dp + 30dp + 30dp + 20dp + 25dp + 2.6 + 1.8 + + 1dp + + 80dp + 65dp + 300dp + 150dp + 70dp + 60dp + 55dp + + 35sp + 0.05 + 450dp + 0dp + 30sp + 0.0 + 394dp + 175dp + + 285sp + + 30dp + + 45dp + \ No newline at end of file diff --git a/app/src/main/res/values-sw720dp/dimens.xml b/app/src/main/res/values-sw720dp/dimens.xml index 4722a3d3..2b037dd0 100644 --- a/app/src/main/res/values-sw720dp/dimens.xml +++ b/app/src/main/res/values-sw720dp/dimens.xml @@ -1,36 +1,36 @@ - - - 25dp - 3dp - 30dp - 30dp - 20dp - 25dp - 2.6 - 2.1 - - 1dp - - 90dp - 75dp - 320dp - 170dp - 90dp - 65dp - 60dp - - 40sp - 0.05 - 590dp - 0dp - 35sp - 0.0 - 394dp - 175dp - - 330sp - - 30dp - - 65dp + + + 25dp + 3dp + 30dp + 30dp + 20dp + 25dp + 2.6 + 2.1 + + 1dp + + 90dp + 75dp + 320dp + 170dp + 90dp + 65dp + 60dp + + 40sp + 0.05 + 590dp + 0dp + 35sp + 0.0 + 394dp + 175dp + + 330sp + + 30dp + + 65dp \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 74e896d7..e9ab523f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,10 +1,10 @@ - - - #263238 - #9e3d9a - #ffffff - #ffffff - #ffffff - #000000 - #504c51 - + + + #263238 + #9e3d9a + #ffffff + #ffffff + #ffffff + #000000 + #504c51 + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index 76c20fb9..957fecd0 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - - - #32D9DC + + + #32D9DC \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 75f8aa0c..6a670858 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,567 +1,567 @@ - - - - - - - Learn Braille - Hello blank fragment - No help message - - Вы не сможете использовать возможности голосового управления - - use_debug_lessons - enable_buzz - enable_toasts - current_user - speech_recognition_enabled - golubina_book_steps_enabled - slate_stylus_steps_enabled - traverse_dots_in_enumeration_order - enable_additional_announcements - practice_use_only_known_materials - extended_accessibility - additional_qrcode_button_enabled - is_write_mode_first - - Порядок точек: "письмо" - Порядок точек: "чтение" - - Правильно! - Неправильно! - Подождите, задание загружается - Ответ: точки %s - Ответ: %s - - точка 1 слева сверху - точка 2 слева посередине - точка 3 слева снизу - точка 4 справа сверху - точка 5 справа посередине - точка 6 справа снизу - - точка 4 слева сверху - точка 5 слева посередине - точка 6 слева снизу - точка 1 справа сверху - точка 2 справа посередине - точка 3 справа снизу - - Введите букву: %s - Введите латинскую букву: %s - Введите греческую букву: %s - Введите цифру: %s - Введите символ: %s - Введите математический символ: %s - Введите символ: Литературная точка - Введите символ: Дефис - Введите запятую - Введите восклицательный знак - Введите вопросительный знак - Введите открывающую кавычку - Введите закрывающую кавычку - Введите левую литературную скобку - Введите правую литературную скобку - Введите звёздочку - Введите двоеточие - Введите точку с запятой - Введите знак ударения - - Введите признак большой буквы греческого алфавита - Введите признак большой буквы латинского алфавита - Введите признак малой буквы латинского алфавита - Введите признак большой буквы русского алфавита - Введите цифровой знак - Введите признак жирного шрифта - Введите признак курсивного шрифта - - Введите знак Плюс - Введите знак Минус - Введите знак умножения точкой - Введите знак умножения крестом - Введите знак деления (углом) - Введите знак деления (двумя точками) - Введите знак равенства - - Буква %s - Латинская буква %s - Греческая буква %s - Цифра %s - Символ %s - Математический символ %s - Литературная точка - Дефис - Запятая - Восклицательный знак - Вопросительный знак - Открывающая кавычка - Закрывающая кавычка - Левая литературная скобка - Правая литературная скобка - Звёздочка - Двоеточие - Точка с запятой - Ударение - - Признак большой греческой буквы - Признак большой латинской буквы - Признак малой латинской буквы - Признак большой буквы русского алфавита - Цифровой знак - Признак жирного шрифта - Признак курсивного шрифта - - Знак Плюс - Знак Минус - Знак умножения точкой - Знак умножения крестом - Знак деления (углом) - Знак деления (двумя точками) - Знак равенства - - Русская буква - Греческая буква - Латинская буква - Цифра - Специальный символ - - - - Установите сканнер qr кодов и попробуйте снова - - - - \? - Подсказка - Далее - Практика: %d из %d - Практика - - Колода: \"%s\"\nПовторять только изученные: включено - - - Колода: \"%s\"\nПовторять только изученные: выключено - - Список колод - - Колода \"%s\" ещё недоступна, пройдите эти карточки в уроках или отключите - \"повторять только изученное\" в разделе \"настройки\". - - - Все символы - Все символы, кроме иностранных букв - Русские буквы - Латинские буквы - Греческие буквы - Цифры - Знаки препинания - Специальные символы - Математические символы - - - -
- В случайном порядке выдаются задания - \"карточки\" с символами.
- В верхней половине экрана выведен символ, - который нужно ввести в шеститочии в нижней половине экрана и нажать кнопку \“далее\” - справа. - &
- По умолчанию выдаются только карточки с символами, изученными в разделе \"Обучение\". Чтобы - повторять любые символы, измените это в настройках приложения. - &
- Шеститочие: письмо/чтение (розовая кнопка справа по центру) - изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - &
- Выход в меню приложения - кнопка вверху слева. - &
- Список колод - кнопка вверху справа. -
- Карточки объединены в колоды по типам символов, например, колода русских букв, колода цифр. - Эта кнопка ведёт в меню выбора колоды. - &
- Вызов справки - кнопка вверху справа, левее списка колод. - &
- Если Вы забыли символ, можно нажать кнопку \"подсказка\" слева внизу экрана. - По нажатию этой кнопки будет выведено сообщение с верными номерами точек, шеститочие будет - заполнено правильными точками и недоступно для переключения. После этого нужно нажать кнопку - \"далее\" и ввести тот же символ ещё раз. - ]]> -
- - - - Выйти - Вернуться в меню - Хотите ли Вы выйти? - - - - HeyHey! - Hello! - Выйти - Начать - - - - ОБУЧЕНИЕ - ПРАКТИКА - СИМВОЛЫ - СТАТИСТИКА - QR-КОД - СПРАВКА - НАСТРОЙКИ - ВЫХОД - %s. Меню - Меню - - Загружаем базу данных. Попробуйте ещё раз! - - Ошибка. Пустой результат сканирования - - - Обучение системе Луи Брайля: главное меню. - &

- Обучение: пошаговые уроки с демонстрацией плоскопечатных и - рельефно-точечных символов крупным шрифтом, вводом символов и поясняющими комментариями. - &
- Практика: Повторение символов. - &
- Символы: Список букв, специальных символов и цифр с указанием точечного состава. - &
- Статистика: Информация о Вашей активности в приложении. - &
- Настройки: Параметры приложения. - &
- В настройках можно включить ещё одну кнопку - \"QR-код\". - &
- QR-код: если у Вас есть набор карточек с текстом Брайля и - QR-кодами на обороте, по нажатию кнопки вы можете отсканировать код и проверить, - что написано на карточке. Для этого необходимо дополнительное приложение. - Если его ещё нет, по нажатию кнопки Вы будете перенаправлены на страницу для скачивания приложения. -
- &
- Находясь в любом разделе приложения, Вы всегда можете вызвать справку по данному разделу, - нажав экранную кнопку \"справка\" в правом верхнем углу экрана. - &
- Из всякого раздела, кроме главного меню приложения, можно выйти в предыдущий, нажав кнопку - \"Перейти вверх\" в левом верхнем углу экрана. - ]]> -
- - - - - Вперёд - Назад - Подсказка - К прогрессу - - Введите точки: %s - - - Выведены точки: %s - - - Урок %d ещё недоступен, пройдите сначала предыдущие уроки. \nВы остановились на уроке %d - - - Текст - Заключение - Введите точки - Введите точки - Изучение - Точки - Уроки - - - - %s - ]]> - - - - Переход к следующему шагу - кнопка \"вперёд\" справа по центру, - к предыдущему - кнопка "назад" слева по центру. - &
- Выход в меню приложения - кнопка \"перейти вверх\" в верхней панели слева. - &
- Вызов справки - кнопка в верхней панели справа. - &
- \"Другие функции\" - кнопка в верхней панели рядом с кнопкой \"справка\". - Нажатие этой кнопки вызывает выпадающее меню с функциями \"навигация по курсу\", то есть - переход к другому уроку, и \"к текущему шагу\", то есть переход к последнему - непройденному шагу. - &
- Чтобы урок стал доступен в меню \"навигация по курсу\", надо пройти все шаги в - предыдущих уроках. В уроке обычно от 20 до 30 шагов. Заголовок шага содержит номер урока, - затем номер шага после точки с запятой, затем тип шага: \"введите точки\", или \"точки\" - (то есть демонстрация символа), или \"текст\". - ]]> -
- - - шаг с вводом символа. - &

- В верхней половине экрана крупным шрифтом выведен символ, который нужно ввести в шеститочии - в нижней половине экрана. - &
- Над кнопкой \"назад\" находится кнопка подсказки, она работает так же, как и в практике. - По нажатию кнопки \"подсказка\" выводится сообщение с правильным ответом, и точки - переключаются в правильное положение. Чтобы пройти то же упражнение после подсказки, нужно - нажать кнопку \"вперёд\". - &
- Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - ]]> -
- - - шаг с вводом точек. -

- Введите в шеститочии на экране точки с указанными номерами. - &
- Если нажать кнопку \"подсказка\", расположенную над кнопкой \"назад\", выведется сообщение - с правильным ответом и точки переключатся в правильное состояние. Чтобы пройти то же - упражнение после подсказки, нажмите кнопку \"вперёд\". - &
- Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - ]]> -
- - - шаг с вводом специального символа. -

- В верхней половине экрана дано описание символа, который нужно ввести в шеститочии - в нижней половине экрана. - &
- Если нажать кнопку \"подсказка\", расположенную над кнопкой \"назад\", выведется сообщение - с правильным ответом и точки переключатся в правильное состояние. Чтобы пройти то же - упражнение после подсказки, нажмите кнопку \"вперёд\". - &
- Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - ]]> -
- - - шаг с демонстрацией символа. -

- В верхней половине экрана крупным шрифтом выведен плоскопечатный символ, а в нижней части - - его представление точечным шрифтом. Нужно изучить точечный символ. - &
- Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - ]]> -
- - - шаг с демонстрацией точек. -

- На экран выведен точечный символ. Внимательно изучите его. - &
- Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - ]]> -
- - - шаг с демонстрацией специального символа. -

- В верхней половине экрана словами описан специальный символ, а в шеститочии в нижней части - - его точечный состав. Внимательно изучите этот символ. - &
- Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки - 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). - ]]> -
- - - шаг с информационным сообщением. -

- Ознакомьтесь с сообщением, выведенном в текстовом поле по центру экрана. - ]]> -
- - - Самый последний шаг курса! Поздравляем! -

- Чтобы выйти в главное меню, нажмите дважды кнопку слева сверху. - ]]> -
- - - - точка один - точка два - точка три - точка четыре - точка пять - точка шесть - - 1 - 2 - 3 - 4 - 5 - 6 - - - - Справка - К текущему шагу - Навигация по курсу - Скрыть это меню - Список колод - - - - Справка - - - - Установлено соединение с Тренажёром Брайля - Разрешение на подключение не выдано - Тренажёр Брайля отключён - Этот аппарат не поддерживается - - - - Список символов - %s: точки %s - Просмотр символа - - - - Помните, что при написании чисел нужно вначале ставить цифровой знак. При написании слов на - латинице или по-гречески внутри русского текста в начале слова надо ставить признак - латинской / греческой буквы. - &
- Нажатие на символ в списке (или двойное нажатие, если Вы используете программу TalkBack) - открывает окно, где этот символ крупным шрифтом выведен на экран. - &
- Из этого раздела можно выйти в главное меню, нажав кнопку \"Перейти вверх\" - в левом верхнем углу экрана. - ]]> -
- - - - Из этого раздела можно выйти обратно к списку символов, нажав кнопку \"Перейти вверх\" - в левом верхнем углу экрана. - ]]> - - - - - Из этого раздела можно выйти обратно к списку символов, нажав кнопку \"Перейти вверх\" - в левом верхнем углу экрана. - ]]> - - - - - Настройки - Всплывающие уведомления - Вибрация - Вибрация в ответ на результат выполнения задания. - Шаги с бумажным пособием - Шаги с брайлевским прибором - - Показывать шаги с обращением к бумажному пособию Голубиной в разделе "Обучение". - - - Показывать шаги для выполнения с брайлевским прибором в разделе "Обучение". - - Включить/отключить краткие всплывающие - подсказки (кроме самых важных). - Обход точек в порядке нумерации - Работает с Андроид 5.1. Если включено, - программа экранного доступа обходит точки шеститочия в порядке 1-2-3-4-5-6, - иначе 1-4-2-5-3-6 (при чтении). При письме, если включено - 4-5-6-1-2-3, иначе 4-1-5-2-6-3. - - Проверка ввода \"на лету\" - Выводить сообщение \"правильно\", - как только в шеститочии введена корректная комбинация, - не дожидаясь нажатия кнопки \"вперёд\". - - Повторять только изученное - - В разделе \"Практика\" повторять только символы, пройденные в разделе \"Обучение\". - - - Автоозвучка текстов - - - Автоматически озвучивать тексты (например, задания, справку) - средством экранного чтения, не дожидаясь фокусировки на текстовом поле. - - - Режим повышенной доступности - - - Крупный шрифт в разделе \"Обучение\". Дополнительные кнопки выхода (удобно при использовании программы - экранного доступа). Шире боковые кнопки в практике и обучении. - - - Возможность сканировать QR-код - - - Отображать в главном меню кнопку \"QR-код\" для игры с карточками Брайля. - - - При вводе столбец с точками 1, 2, 3 справа - - - При заходе в практику или шаг с вводом точек столбец с точками 1, 2, 3 находится справа, - как при письме на брайлевском приборе. - - - - - - - Из этого раздела можно выйти в главное меню, нажав кнопку \"Перейти вверх\" (стрелка влево) - в левом верхнем углу экрана. - ]]> - - - Статистика - Практика: - Пройдено карточек - Потребовалось подсказок - Потрачено попыток - Обучение: - Пройдено шагов - Пройдено шагов с вводом - За последние 7 дней - За последние 30 дней - Шеститочие: письмо/чтение -
+ + + + + + + Learn Braille + Hello blank fragment + No help message + + Вы не сможете использовать возможности голосового управления + + use_debug_lessons + enable_buzz + enable_toasts + current_user + speech_recognition_enabled + golubina_book_steps_enabled + slate_stylus_steps_enabled + traverse_dots_in_enumeration_order + enable_additional_announcements + practice_use_only_known_materials + extended_accessibility + additional_qrcode_button_enabled + is_write_mode_first + + Порядок точек: "письмо" + Порядок точек: "чтение" + + Правильно! + Неправильно! + Подождите, задание загружается + Ответ: точки %s + Ответ: %s + + точка 1 слева сверху + точка 2 слева посередине + точка 3 слева снизу + точка 4 справа сверху + точка 5 справа посередине + точка 6 справа снизу + + точка 4 слева сверху + точка 5 слева посередине + точка 6 слева снизу + точка 1 справа сверху + точка 2 справа посередине + точка 3 справа снизу + + Введите букву: %s + Введите латинскую букву: %s + Введите греческую букву: %s + Введите цифру: %s + Введите символ: %s + Введите математический символ: %s + Введите символ: Литературная точка + Введите символ: Дефис + Введите запятую + Введите восклицательный знак + Введите вопросительный знак + Введите открывающую кавычку + Введите закрывающую кавычку + Введите левую литературную скобку + Введите правую литературную скобку + Введите звёздочку + Введите двоеточие + Введите точку с запятой + Введите знак ударения + + Введите признак большой буквы греческого алфавита + Введите признак большой буквы латинского алфавита + Введите признак малой буквы латинского алфавита + Введите признак большой буквы русского алфавита + Введите цифровой знак + Введите признак жирного шрифта + Введите признак курсивного шрифта + + Введите знак Плюс + Введите знак Минус + Введите знак умножения точкой + Введите знак умножения крестом + Введите знак деления (углом) + Введите знак деления (двумя точками) + Введите знак равенства + + Буква %s + Латинская буква %s + Греческая буква %s + Цифра %s + Символ %s + Математический символ %s + Литературная точка + Дефис + Запятая + Восклицательный знак + Вопросительный знак + Открывающая кавычка + Закрывающая кавычка + Левая литературная скобка + Правая литературная скобка + Звёздочка + Двоеточие + Точка с запятой + Ударение + + Признак большой греческой буквы + Признак большой латинской буквы + Признак малой латинской буквы + Признак большой буквы русского алфавита + Цифровой знак + Признак жирного шрифта + Признак курсивного шрифта + + Знак Плюс + Знак Минус + Знак умножения точкой + Знак умножения крестом + Знак деления (углом) + Знак деления (двумя точками) + Знак равенства + + Русская буква + Греческая буква + Латинская буква + Цифра + Специальный символ + + + + Установите сканнер qr кодов и попробуйте снова + + + + \? + Подсказка + Далее + Практика: %d из %d + Практика + + Колода: \"%s\"\nПовторять только изученные: включено + + + Колода: \"%s\"\nПовторять только изученные: выключено + + Список колод + + Колода \"%s\" ещё недоступна, пройдите эти карточки в уроках или отключите + \"повторять только изученное\" в разделе \"настройки\". + + + Все символы + Все символы, кроме иностранных букв + Русские буквы + Латинские буквы + Греческие буквы + Цифры + Знаки препинания + Специальные символы + Математические символы + + + +
+ В случайном порядке выдаются задания - \"карточки\" с символами.
+ В верхней половине экрана выведен символ, + который нужно ввести в шеститочии в нижней половине экрана и нажать кнопку \“далее\” + справа. + &
+ По умолчанию выдаются только карточки с символами, изученными в разделе \"Обучение\". Чтобы + повторять любые символы, измените это в настройках приложения. + &
+ Шеститочие: письмо/чтение (розовая кнопка справа по центру) - изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + &
+ Выход в меню приложения - кнопка вверху слева. + &
+ Список колод - кнопка вверху справа. +
+ Карточки объединены в колоды по типам символов, например, колода русских букв, колода цифр. + Эта кнопка ведёт в меню выбора колоды. + &
+ Вызов справки - кнопка вверху справа, левее списка колод. + &
+ Если Вы забыли символ, можно нажать кнопку \"подсказка\" слева внизу экрана. + По нажатию этой кнопки будет выведено сообщение с верными номерами точек, шеститочие будет + заполнено правильными точками и недоступно для переключения. После этого нужно нажать кнопку + \"далее\" и ввести тот же символ ещё раз. + ]]> +
+ + + + Выйти + Вернуться в меню + Хотите ли Вы выйти? + + + + HeyHey! + Hello! + Выйти + Начать + + + + ОБУЧЕНИЕ + ПРАКТИКА + СИМВОЛЫ + СТАТИСТИКА + QR-КОД + СПРАВКА + НАСТРОЙКИ + ВЫХОД + %s. Меню + Меню + + Загружаем базу данных. Попробуйте ещё раз! + + Ошибка. Пустой результат сканирования + + + Обучение системе Луи Брайля: главное меню. + &

+ Обучение: пошаговые уроки с демонстрацией плоскопечатных и + рельефно-точечных символов крупным шрифтом, вводом символов и поясняющими комментариями. + &
+ Практика: Повторение символов. + &
+ Символы: Список букв, специальных символов и цифр с указанием точечного состава. + &
+ Статистика: Информация о Вашей активности в приложении. + &
+ Настройки: Параметры приложения. + &
+ В настройках можно включить ещё одну кнопку - \"QR-код\". + &
+ QR-код: если у Вас есть набор карточек с текстом Брайля и + QR-кодами на обороте, по нажатию кнопки вы можете отсканировать код и проверить, + что написано на карточке. Для этого необходимо дополнительное приложение. + Если его ещё нет, по нажатию кнопки Вы будете перенаправлены на страницу для скачивания приложения. +
+ &
+ Находясь в любом разделе приложения, Вы всегда можете вызвать справку по данному разделу, + нажав экранную кнопку \"справка\" в правом верхнем углу экрана. + &
+ Из всякого раздела, кроме главного меню приложения, можно выйти в предыдущий, нажав кнопку + \"Перейти вверх\" в левом верхнем углу экрана. + ]]> +
+ + + + + Вперёд + Назад + Подсказка + К прогрессу + + Введите точки: %s + + + Выведены точки: %s + + + Урок %d ещё недоступен, пройдите сначала предыдущие уроки. \nВы остановились на уроке %d + + + Текст + Заключение + Введите точки + Введите точки + Изучение + Точки + Уроки + + + + %s + ]]> + + + + Переход к следующему шагу - кнопка \"вперёд\" справа по центру, + к предыдущему - кнопка "назад" слева по центру. + &
+ Выход в меню приложения - кнопка \"перейти вверх\" в верхней панели слева. + &
+ Вызов справки - кнопка в верхней панели справа. + &
+ \"Другие функции\" - кнопка в верхней панели рядом с кнопкой \"справка\". + Нажатие этой кнопки вызывает выпадающее меню с функциями \"навигация по курсу\", то есть + переход к другому уроку, и \"к текущему шагу\", то есть переход к последнему + непройденному шагу. + &
+ Чтобы урок стал доступен в меню \"навигация по курсу\", надо пройти все шаги в + предыдущих уроках. В уроке обычно от 20 до 30 шагов. Заголовок шага содержит номер урока, + затем номер шага после точки с запятой, затем тип шага: \"введите точки\", или \"точки\" + (то есть демонстрация символа), или \"текст\". + ]]> +
+ + + шаг с вводом символа. + &

+ В верхней половине экрана крупным шрифтом выведен символ, который нужно ввести в шеститочии + в нижней половине экрана. + &
+ Над кнопкой \"назад\" находится кнопка подсказки, она работает так же, как и в практике. + По нажатию кнопки \"подсказка\" выводится сообщение с правильным ответом, и точки + переключаются в правильное положение. Чтобы пройти то же упражнение после подсказки, нужно + нажать кнопку \"вперёд\". + &
+ Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + ]]> +
+ + + шаг с вводом точек. +

+ Введите в шеститочии на экране точки с указанными номерами. + &
+ Если нажать кнопку \"подсказка\", расположенную над кнопкой \"назад\", выведется сообщение + с правильным ответом и точки переключатся в правильное состояние. Чтобы пройти то же + упражнение после подсказки, нажмите кнопку \"вперёд\". + &
+ Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + ]]> +
+ + + шаг с вводом специального символа. +

+ В верхней половине экрана дано описание символа, который нужно ввести в шеститочии + в нижней половине экрана. + &
+ Если нажать кнопку \"подсказка\", расположенную над кнопкой \"назад\", выведется сообщение + с правильным ответом и точки переключатся в правильное состояние. Чтобы пройти то же + упражнение после подсказки, нажмите кнопку \"вперёд\". + &
+ Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + ]]> +
+ + + шаг с демонстрацией символа. +

+ В верхней половине экрана крупным шрифтом выведен плоскопечатный символ, а в нижней части - + его представление точечным шрифтом. Нужно изучить точечный символ. + &
+ Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + ]]> +
+ + + шаг с демонстрацией точек. +

+ На экран выведен точечный символ. Внимательно изучите его. + &
+ Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + ]]> +
+ + + шаг с демонстрацией специального символа. +

+ В верхней половине экрана словами описан специальный символ, а в шеститочии в нижней части + - его точечный состав. Внимательно изучите этот символ. + &
+ Шеститочие: письмо/чтение (розовая кнопка справа над кнопкой "вперёд") изменяет порядок столбцов: точки + 1, 2, 3 справа (как при письме на брайлевском приборе) или слева (как при чтении). + ]]> +
+ + + шаг с информационным сообщением. +

+ Ознакомьтесь с сообщением, выведенном в текстовом поле по центру экрана. + ]]> +
+ + + Самый последний шаг курса! Поздравляем! +

+ Чтобы выйти в главное меню, нажмите дважды кнопку слева сверху. + ]]> +
+ + + + точка один + точка два + точка три + точка четыре + точка пять + точка шесть + + 1 + 2 + 3 + 4 + 5 + 6 + + + + Справка + К текущему шагу + Навигация по курсу + Скрыть это меню + Список колод + + + + Справка + + + + Установлено соединение с Тренажёром Брайля + Разрешение на подключение не выдано + Тренажёр Брайля отключён + Этот аппарат не поддерживается + + + + Список символов + %s: точки %s + Просмотр символа + + + + Помните, что при написании чисел нужно вначале ставить цифровой знак. При написании слов на + латинице или по-гречески внутри русского текста в начале слова надо ставить признак + латинской / греческой буквы. + &
+ Нажатие на символ в списке (или двойное нажатие, если Вы используете программу TalkBack) + открывает окно, где этот символ крупным шрифтом выведен на экран. + &
+ Из этого раздела можно выйти в главное меню, нажав кнопку \"Перейти вверх\" + в левом верхнем углу экрана. + ]]> +
+ + + + Из этого раздела можно выйти обратно к списку символов, нажав кнопку \"Перейти вверх\" + в левом верхнем углу экрана. + ]]> + + + + + Из этого раздела можно выйти обратно к списку символов, нажав кнопку \"Перейти вверх\" + в левом верхнем углу экрана. + ]]> + + + + + Настройки + Всплывающие уведомления + Вибрация + Вибрация в ответ на результат выполнения задания. + Шаги с бумажным пособием + Шаги с брайлевским прибором + + Показывать шаги с обращением к бумажному пособию Голубиной в разделе "Обучение". + + + Показывать шаги для выполнения с брайлевским прибором в разделе "Обучение". + + Включить/отключить краткие всплывающие + подсказки (кроме самых важных). + Обход точек в порядке нумерации + Работает с Андроид 5.1. Если включено, + программа экранного доступа обходит точки шеститочия в порядке 1-2-3-4-5-6, + иначе 1-4-2-5-3-6 (при чтении). При письме, если включено - 4-5-6-1-2-3, иначе 4-1-5-2-6-3. + + Проверка ввода \"на лету\" + Выводить сообщение \"правильно\", + как только в шеститочии введена корректная комбинация, + не дожидаясь нажатия кнопки \"вперёд\". + + Повторять только изученное + + В разделе \"Практика\" повторять только символы, пройденные в разделе \"Обучение\". + + + Автоозвучка текстов + + + Автоматически озвучивать тексты (например, задания, справку) + средством экранного чтения, не дожидаясь фокусировки на текстовом поле. + + + Режим повышенной доступности + + + Крупный шрифт в разделе \"Обучение\". Дополнительные кнопки выхода (удобно при использовании программы + экранного доступа). Шире боковые кнопки в практике и обучении. + + + Возможность сканировать QR-код + + + Отображать в главном меню кнопку \"QR-код\" для игры с карточками Брайля. + + + При вводе столбец с точками 1, 2, 3 справа + + + При заходе в практику или шаг с вводом точек столбец с точками 1, 2, 3 находится справа, + как при письме на брайлевском приборе. + + + + + + + Из этого раздела можно выйти в главное меню, нажав кнопку \"Перейти вверх\" (стрелка влево) + в левом верхнем углу экрана. + ]]> + + + Статистика + Практика: + Пройдено карточек + Потребовалось подсказок + Потрачено попыток + Обучение: + Пройдено шагов + Пройдено шагов с вводом + За последние 7 дней + За последние 30 дней + Шеститочие: письмо/чтение +
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e8b9706a..209222ad 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,92 +1,92 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/com/github/braillesystems/learnbraille/data/entities/BrailleDotsTest.kt b/app/src/test/java/com/github/braillesystems/learnbraille/data/entities/BrailleDotsTest.kt index ce2e287e..5c967782 100644 --- a/app/src/test/java/com/github/braillesystems/learnbraille/data/entities/BrailleDotsTest.kt +++ b/app/src/test/java/com/github/braillesystems/learnbraille/data/entities/BrailleDotsTest.kt @@ -1,15 +1,15 @@ -package com.github.braillesystems.learnbraille.data.entities - -import org.junit.Assert.assertEquals -import org.junit.Test - -class BrailleDotsTest { - - @Test - fun spelling() { - assertEquals( - "1, 3, 6", - BrailleDots(b1 = BrailleDot.F, b3 = BrailleDot.F, b6 = BrailleDot.F).spelling - ) - } -} +package com.github.braillesystems.learnbraille.data.entities + +import org.junit.Assert.assertEquals +import org.junit.Test + +class BrailleDotsTest { + + @Test + fun spelling() { + assertEquals( + "1, 3, 6", + BrailleDots(b1 = BrailleDot.F, b3 = BrailleDot.F, b6 = BrailleDot.F).spelling + ) + } +} diff --git a/app/src/test/java/com/github/braillesystems/learnbraille/utils/UtilsTest.kt b/app/src/test/java/com/github/braillesystems/learnbraille/utils/UtilsTest.kt index c8aa7b34..171f1442 100644 --- a/app/src/test/java/com/github/braillesystems/learnbraille/utils/UtilsTest.kt +++ b/app/src/test/java/com/github/braillesystems/learnbraille/utils/UtilsTest.kt @@ -1,37 +1,37 @@ -package com.github.braillesystems.learnbraille.utils - -import kotlinx.serialization.Serializable -import org.junit.Assert.assertEquals -import org.junit.Test - -@Serializable -sealed class S { - abstract val s: String -} - -@Serializable -data class A(override val s: String) : S() - -@Serializable -data class B(val i: Long, override val s: String) : S() - -class UtilsTest { - - @Test - fun serializationBasic() { - @Serializable - data class D(val s: String, val i: Int) - - val d = D("Wow", 100500) - assertEquals(d, parse(D.serializer(), stringify(D.serializer(), d))) - } - - @Test - fun serializationVirtual() { - val s1: S = A("wow") - assertEquals(s1 as A, parse(S.serializer(), stringify(S.serializer(), s1))) - val s2: S = B(100500, "aaa") - assertEquals(s2 as B, parse(S.serializer(), stringify(S.serializer(), s2))) - assertEquals("wow", s1.s) - } -} +package com.github.braillesystems.learnbraille.utils + +import kotlinx.serialization.Serializable +import org.junit.Assert.assertEquals +import org.junit.Test + +@Serializable +sealed class S { + abstract val s: String +} + +@Serializable +data class A(override val s: String) : S() + +@Serializable +data class B(val i: Long, override val s: String) : S() + +class UtilsTest { + + @Test + fun serializationBasic() { + @Serializable + data class D(val s: String, val i: Int) + + val d = D("Wow", 100500) + assertEquals(d, parse(D.serializer(), stringify(D.serializer(), d))) + } + + @Test + fun serializationVirtual() { + val s1: S = A("wow") + assertEquals(s1 as A, parse(S.serializer(), stringify(S.serializer(), s1))) + val s2: S = B(100500, "aaa") + assertEquals(s2 as B, parse(S.serializer(), stringify(S.serializer(), s2))) + assertEquals("wow", s1.s) + } +} diff --git a/build.gradle b/build.gradle index 44d79739..4dcf30c4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,38 +1,38 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - ext { - // Downgraded from '1.3.72', annotation processing error in compiler - // Then upgraded to '1.3.61' because of https://github.com/Kotlin/kotlinx.serialization/issues/576 - kotlin_version = '1.3.61' - version_navigation = '1.0.0' - version_room = "2.2.5" - version_dokka="0.9.18" - } - repositories { - google() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$version_navigation" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:$version_dokka" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - jcenter() - maven { url "https://jitpack.io" } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + // Downgraded from '1.3.72', annotation processing error in compiler + // Then upgraded to '1.3.61' because of https://github.com/Kotlin/kotlinx.serialization/issues/576 + kotlin_version = '1.3.61' + version_navigation = '1.0.0' + version_room = "2.2.5" + version_dokka="0.9.18" + } + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$version_navigation" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:$version_dokka" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + maven { url "https://jitpack.io" } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties index 5efc49fb..23339e0d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,21 +1,21 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/gradlew.bat b/gradlew.bat index e95643d6..f9553162 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle index 322af9c7..a44c2430 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':app' -rootProject.name='LearnBraille' +include ':app' +rootProject.name='LearnBraille' From 9c4e9295beb1ca309a19470ffcc2a1504887095a Mon Sep 17 00:00:00 2001 From: zuevval Date: Wed, 27 Jan 2021 12:27:00 +0300 Subject: [PATCH 02/58] (#264) add side margins to text views in show/input steps --- app/src/main/res/layout/fragment_card.xml | 6 +----- .../main/res/layout/fragment_lessons_input_dots.xml | 6 +----- .../res/layout/fragment_lessons_input_marker.xml | 6 +----- .../main/res/layout/fragment_lessons_show_dots.xml | 6 +----- .../main/res/layout/fragment_lessons_show_marker.xml | 6 +----- app/src/main/res/layout/fragment_marker_view.xml | 6 +----- app/src/main/res/values/dimens.xml | 5 ----- app/src/main/res/values/styles.xml | 12 ++++++++++++ 8 files changed, 18 insertions(+), 35 deletions(-) diff --git a/app/src/main/res/layout/fragment_card.xml b/app/src/main/res/layout/fragment_card.xml index f5af9814..801f13fb 100644 --- a/app/src/main/res/layout/fragment_card.xml +++ b/app/src/main/res/layout/fragment_card.xml @@ -25,11 +25,7 @@ 270dp 0dp 18sp - 0.0 - 394dp 175dp - 0.5 - 10dp - 0.2 170sp 5dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e8b9706a..ec268b1b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -61,12 +61,24 @@ + + + + -
+
\ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9fa69657..bb82d3a3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip From 87399fc43d94422153a752fffda74282cd5fc32a Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Fri, 20 Aug 2021 23:10:01 +0300 Subject: [PATCH 04/58] Add teacher mode setting --- .../ui/screens/settings/SettingsFragment.kt | 48 +++++++++++++++++++ app/src/main/res/values/strings.xml | 19 ++++++++ app/src/main/res/xml/settings_hierarchy.xml | 8 ++++ 3 files changed, 75 insertions(+) diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt index a109a759..406b3cae 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt @@ -1,11 +1,59 @@ package com.github.braillesystems.learnbraille.ui.screens.settings +import android.app.AlertDialog import android.os.Bundle +import android.text.InputType +import android.widget.EditText +import androidx.preference.Preference +import androidx.preference.SwitchPreferenceCompat import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.utils.toast +import timber.log.Timber class SettingsFragment : androidx.preference.PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.settings_hierarchy, rootKey) + + val preference = findPreference( + getString(R.string.preference_teacher_mode_enabled) + ) ?: error("No teacher mode preference found") + + preference.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _, newValue -> + require(preference is SwitchPreferenceCompat) + require(newValue is Boolean) + if (newValue) { + askCode { + val code = 8436 + if (it != code) { + Timber.i("Wrong code: $it, but expected $code") + toast(getString(R.string.preference_wrong_code_teacher_mode)) + } else { + Timber.i("Correct code: $code") + preference.isChecked = true + } + } + false + } else { + true + } + } + } + + private fun askCode(block: (Int?) -> Unit) { + val input = EditText(context).apply { + inputType = InputType.TYPE_CLASS_NUMBER + } + AlertDialog.Builder(context) + .setTitle(getString(R.string.preference_code_dialog_title)) + .setView(input) + .setPositiveButton(getString(R.string.preference_code_dialog_ok)) { _, _ -> + block(input.text.toString().toIntOrNull()) + } + .setNegativeButton(getString(R.string.preference_code_dialog_cancel)) { dialog, _ -> + dialog.cancel() + } + .show() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a670858..e8926fb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,6 +23,7 @@ extended_accessibility additional_qrcode_button_enabled is_write_mode_first + teacher_mode_enabled Порядок точек: "письмо" Порядок точек: "чтение" @@ -540,6 +541,24 @@ При заходе в практику или шаг с вводом точек столбец с точками 1, 2, 3 находится справа, как при письме на брайлевском приборе.
+ + Режим преподавателя + + + В режиме преподавателя доступны все уроки. Для активации потребуется ввести код преподавателя. + + + Неверный код, попробуйте ещё раз + + + Введите код преподавателя + + + Ввод + + + Отмена + diff --git a/app/src/main/res/xml/settings_hierarchy.xml b/app/src/main/res/xml/settings_hierarchy.xml index d616b703..16ae6162 100644 --- a/app/src/main/res/xml/settings_hierarchy.xml +++ b/app/src/main/res/xml/settings_hierarchy.xml @@ -89,4 +89,12 @@ android:title="@string/preference_title_additional_announcements" app:iconSpaceReserved="false" /> + + \ No newline at end of file From 0e4b4363fc90fcb59f12b4963df4d9951257a614 Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Fri, 20 Aug 2021 23:10:31 +0300 Subject: [PATCH 05/58] Hide stats --- .../data/repository/PreferenceRepository.kt | 8 ++++++++ .../learnbraille/ui/screens/menu/MenuFragment.kt | 14 +++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/PreferenceRepository.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/PreferenceRepository.kt index 3ce51ad4..8e15e839 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/PreferenceRepository.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/PreferenceRepository.kt @@ -25,6 +25,7 @@ interface PreferenceRepository { val extendedAccessibilityEnabled: Boolean val additionalQrCodeButtonEnabled: Boolean val isWriteModeFirst: Boolean + val teacherModeEnabled: Boolean val currentUserId: DBid suspend fun getCurrentUser(): User @@ -122,6 +123,13 @@ class PreferenceRepositoryImpl( ) } + override val teacherModeEnabled: Boolean by logged { + context.preferences.getBoolean( + context.getString(R.string.preference_teacher_mode_enabled), + false + ) + } + override val currentUserId: DBid by logged { context.preferences.getLong( context.getString(R.string.preference_current_user), 1 diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt index 1488508d..e6366953 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt @@ -57,11 +57,15 @@ class MenuFragment : AbstractFragmentWithHelp(R.string.menu_help) { navigate(R.id.action_menuFragment_to_browserFragment) }) - binding.statsButton.also { - buttons += it - }.setOnClickListener(interruptingOnClickListener { - navigate(R.id.action_menuFragment_to_statsFragment) - }) + if (!preferenceRepository.teacherModeEnabled) { + binding.statsButton.also { + buttons += it + }.setOnClickListener(interruptingOnClickListener { + navigate(R.id.action_menuFragment_to_statsFragment) + }) + } else { + binding.statsButton.visibility = View.GONE + } if (preferenceRepository.additionalQrCodeButtonEnabled) { binding.qrPracticeButton.also { From bc46802897385bf1c6f4810f90e43af3c9ed218b Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Fri, 20 Aug 2021 23:18:23 +0300 Subject: [PATCH 06/58] Do not calculate stats --- .../learnbraille/LearnBrailleApplication.kt | 14 +++++++------- .../data/repository/ActionsRepository.kt | 11 +++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt b/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt index 4615ba07..41d6256b 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/LearnBrailleApplication.kt @@ -25,13 +25,6 @@ class LearnBrailleApplication : Application() { single { LearnBrailleDatabase.buildDatabase(this@LearnBrailleApplication) } - factory { - ActionsRepositoryImpl(get().actionDao) - } - factory { - ActionsRepositoryImpl(get().actionDao) - } - factory { PreferenceRepositoryImpl( this@LearnBrailleApplication, @@ -45,6 +38,13 @@ class LearnBrailleApplication : Application() { ) } + factory { + ActionsRepositoryImpl(get().actionDao, get()) + } + factory { + ActionsRepositoryImpl(get().actionDao, get()) + } + factory { val db = get() MaterialsRepositoryImpl(db.deckDao, db.cardDao, get()) diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepository.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepository.kt index 83dd9011..c77f007c 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepository.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepository.kt @@ -22,14 +22,17 @@ interface MutableActionsRepository : ActionsRepository { class ActionsRepositoryImpl( private val actionsDao: ActionDao, + private val preferenceRepository: PreferenceRepository, private val getCurrDate: () -> Date = { Date() }, private val keepActionsTime: Days = Days(30) ) : MutableActionsRepository { - override suspend fun addAction(type: ActionType) = - actionsDao.insert( - Action(type = type, date = getCurrDate()) - ) + override suspend fun addAction(type: ActionType) { + if (!preferenceRepository.teacherModeEnabled) { + val action = Action(type = type, date = getCurrDate()) + actionsDao.insert(action) + } + } override suspend fun clearAllStats() = actionsDao.clear() From 82df7c8ca05c056a253c82147d29701fd1d62e70 Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Fri, 20 Aug 2021 23:19:45 +0300 Subject: [PATCH 07/58] Remove "To current step" in teacher mode --- .../theory/steps/AbstractStepFragment.kt | 14 ++++++---- .../main/res/menu/steps_menu_hide_no_curr.xml | 26 +++++++++++++++++++ app/src/main/res/menu/steps_menu_no_curr.xml | 19 ++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/menu/steps_menu_hide_no_curr.xml create mode 100644 app/src/main/res/menu/steps_menu_no_curr.xml diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/AbstractStepFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/AbstractStepFragment.kt index 1d9bc363..58bf54e8 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/AbstractStepFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/AbstractStepFragment.kt @@ -17,9 +17,9 @@ import com.github.braillesystems.learnbraille.ui.screens.theory.getStepArg import com.github.braillesystems.learnbraille.ui.screens.theory.toCurrentStep import com.github.braillesystems.learnbraille.ui.screens.theory.toNextStep import com.github.braillesystems.learnbraille.ui.screens.theory.toPrevStep -import com.github.braillesystems.learnbraille.utils.checkedAnnounce import com.github.braillesystems.learnbraille.utils.navigate import com.github.braillesystems.learnbraille.utils.title +import com.github.braillesystems.learnbraille.utils.unreachable interface StepBinding { val prevButton: Button? get() = null @@ -82,13 +82,17 @@ abstract class AbstractStepFragment(helpMsgId: HelpMsgId) : AbstractFragmentWith protected open fun iniStepHelper() = Unit - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = inflater.inflate( - if (preferenceRepository.extendedAccessibilityEnabled) R.menu.steps_menu_hide - else R.menu.steps_menu, + when (preferenceRepository.extendedAccessibilityEnabled to preferenceRepository.teacherModeEnabled) { + true to true -> R.menu.steps_menu_hide_no_curr + true to false -> R.menu.steps_menu_hide + false to true -> R.menu.steps_menu_no_curr + false to false -> R.menu.steps_menu + else -> unreachable + }, menu ) - } override fun onOptionsItemSelected(item: MenuItem) = false.also { when (item.itemId) { diff --git a/app/src/main/res/menu/steps_menu_hide_no_curr.xml b/app/src/main/res/menu/steps_menu_hide_no_curr.xml new file mode 100644 index 00000000..c1d589ad --- /dev/null +++ b/app/src/main/res/menu/steps_menu_hide_no_curr.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/steps_menu_no_curr.xml b/app/src/main/res/menu/steps_menu_no_curr.xml new file mode 100644 index 00000000..a485f443 --- /dev/null +++ b/app/src/main/res/menu/steps_menu_no_curr.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file From 2d4209c5b8ef1de207c311c729518c2cb347a3b9 Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Fri, 20 Aug 2021 23:20:52 +0300 Subject: [PATCH 08/58] Allow to go to any lesson in the teacher mode --- .../learnbraille/data/entities/Steps.kt | 9 +++++++ .../data/repository/TheoryRepository.kt | 25 +++++++++++++------ .../theory/lessons/LessonsListFragment.kt | 2 +- .../steps/input/AbstractInputStepFragment.kt | 5 +++- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Steps.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Steps.kt index fe68006c..58cfc949 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Steps.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/Steps.kt @@ -1,6 +1,7 @@ package com.github.braillesystems.learnbraille.data.entities import androidx.room.* +import com.github.braillesystems.learnbraille.data.dsl.CourseID import com.github.braillesystems.learnbraille.utils.compareTo import kotlinx.serialization.Serializable @@ -37,6 +38,14 @@ interface StepDao { @Query("select * from steps where id = :id") suspend fun step(id: DBid): Step? + @Query( + """ + select * from steps + where course_id = :courseId and lesson_id = :lessonId + """ + ) + suspend fun step(courseId: DBid, lessonId: DBid): Step? + @Query( """ select steps.* from current_step as cs diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt index c3582605..c3887cac 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt @@ -87,14 +87,23 @@ class TheoryRepositoryImpl( /** * LessonId is supposed to exist for this courseId. */ - override suspend fun lastLessonOrCurrentStepAndMove(courseId: DBid, lessonId: DBid): Step = - stepDao - .lastStep(preferenceRepository.currentUserId, courseId, lessonId) - ?.also { updateLast(it) } - ?: error( - "No such lessonId ($lessonId) exists for course ($courseId) " + - "or current step is behind lesson with such lessonId" - ) + override suspend fun lastLessonOrCurrentStepAndMove(courseId: DBid, lessonId: DBid): Step { + val lastStep = stepDao.lastStep(preferenceRepository.currentUserId, courseId, lessonId) + if (lastStep != null) { + updateLast(lastStep) + return lastStep + } + if (preferenceRepository.teacherModeEnabled) { + val step = stepDao.step(courseId, lessonId) + ?: error("No such lessonId ($lessonId) exists for course ($courseId)") + updateLast(step) + return step + } + error( + "No such lessonId ($lessonId) exists for course ($courseId) " + + "or current step is behind lesson with such lessonId" + ) + } override suspend fun currentStep(courseId: DBid): Step = stepDao diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/lessons/LessonsListFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/lessons/LessonsListFragment.kt index 37addef9..2a7e2784 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/lessons/LessonsListFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/lessons/LessonsListFragment.kt @@ -60,7 +60,7 @@ class LessonsListFragment : AbstractFragment() { if (item.id == last.lessonId) Typeface.BOLD else Typeface.NORMAL ) - if (item.id <= curr.lessonId) { + if (preferenceRepository.teacherModeEnabled || item.id <= curr.lessonId) { clickListener = activeListener lessonName.setTextColor( ContextCompat.getColor( diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/AbstractInputStepFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/AbstractInputStepFragment.kt index ca7176e1..d5dd65c7 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/AbstractInputStepFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/AbstractInputStepFragment.kt @@ -88,7 +88,10 @@ abstract class AbstractInputStepFragment(helpMsgId: HelpMsgId) : AbstractStepFra ) } if (userTouchedDots) notify() - else toNextStep(step, markThisAsPassed = false) { notify() } + else toNextStep( + step, + markThisAsPassed = preferenceRepository.teacherModeEnabled + ) { notify() } } viewModel.observeEventPassHint( From 3a9f3e90e133ef22e2a3fc38c21d1b82e5e7cfc1 Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Sun, 22 Aug 2021 00:39:06 +0300 Subject: [PATCH 09/58] Restore current lesson after disabling teacher mode --- .../data/repository/TheoryRepository.kt | 4 ++ .../ui/screens/menu/MenuFragment.kt | 4 +- .../ui/screens/settings/SettingsFragment.kt | 47 ++++++++++++++++++- .../ui/screens/theory/TheoryFreeNavigation.kt | 9 +++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt index c3887cac..fc3e1ab9 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt @@ -13,6 +13,7 @@ interface TheoryRepository { } interface MutableTheoryRepository : TheoryRepository { + suspend fun setCurrentStep(curr: CurrentStep) suspend fun nextStepAndMove(thisStep: Step, markThisAsPassed: Boolean = false): Step? suspend fun prevStepAndMove(thisStep: Step): Step? suspend fun currentStepAndMove(courseId: DBid): Step @@ -38,6 +39,9 @@ class TheoryRepositoryImpl( else StepAnnotation.slateStylusRequired ) + override suspend fun setCurrentStep(curr: CurrentStep) = + currentStepDao.update(curr) + @Suppress("ReturnCount") override suspend fun nextStepAndMove(thisStep: Step, markThisAsPassed: Boolean): Step? { val next = stepDao diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt index e6366953..0898f9ab 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/menu/MenuFragment.kt @@ -14,7 +14,7 @@ import com.github.braillesystems.learnbraille.R import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase import com.github.braillesystems.learnbraille.databinding.FragmentMenuBinding import com.github.braillesystems.learnbraille.ui.screens.AbstractFragmentWithHelp -import com.github.braillesystems.learnbraille.ui.screens.theory.toLastCourseStep +import com.github.braillesystems.learnbraille.ui.screens.theory.toLastOrCurrCourseStep import com.github.braillesystems.learnbraille.utils.* import com.google.android.material.button.MaterialButton import org.koin.android.ext.android.inject @@ -42,7 +42,7 @@ class MenuFragment : AbstractFragmentWithHelp(R.string.menu_help) { binding.lessonsButton.also { buttons += it }.setOnClickListener(interruptingOnClickListener { - toLastCourseStep(COURSE.id) + toLastOrCurrCourseStep(COURSE.id) }) binding.practiceButton.also { diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt index 406b3cae..45d2565d 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/settings/SettingsFragment.kt @@ -4,14 +4,26 @@ import android.app.AlertDialog import android.os.Bundle import android.text.InputType import android.widget.EditText +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat +import com.github.braillesystems.learnbraille.COURSE import com.github.braillesystems.learnbraille.R +import com.github.braillesystems.learnbraille.data.entities.CurrentStep +import com.github.braillesystems.learnbraille.data.repository.MutableTheoryRepository +import com.github.braillesystems.learnbraille.data.repository.PreferenceRepository +import com.github.braillesystems.learnbraille.utils.contextNotNull +import com.github.braillesystems.learnbraille.utils.preferences import com.github.braillesystems.learnbraille.utils.toast +import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject import timber.log.Timber class SettingsFragment : androidx.preference.PreferenceFragmentCompat() { + private val preferenceRepository: PreferenceRepository by inject() + private val theoryRepository: MutableTheoryRepository by inject() + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.settings_hierarchy, rootKey) @@ -23,6 +35,9 @@ class SettingsFragment : androidx.preference.PreferenceFragmentCompat() { Preference.OnPreferenceChangeListener { _, newValue -> require(preference is SwitchPreferenceCompat) require(newValue is Boolean) + + val saveCurrStepPreference = + "save_curr_step_teacher_mode_${preferenceRepository.currentUserId}" if (newValue) { askCode { val code = 8436 @@ -31,12 +46,40 @@ class SettingsFragment : androidx.preference.PreferenceFragmentCompat() { toast(getString(R.string.preference_wrong_code_teacher_mode)) } else { Timber.i("Correct code: $code") - preference.isChecked = true + lifecycleScope.launch { + val curr = theoryRepository.currentStep(COURSE.id) + with(contextNotNull.preferences.edit()) { + putString( + saveCurrStepPreference, + listOf(curr.courseId, curr.lessonId, curr.id) + .joinToString(separator = ",") + ) + apply() + } + preference.isChecked = true + } } } false } else { - true + lifecycleScope.launch { + contextNotNull.preferences + .getString(saveCurrStepPreference, null) + ?.split(",") + ?.map { it.toLong() } + ?.let { (courseId, lessonId, stepId) -> + theoryRepository.setCurrentStep( + CurrentStep( + preferenceRepository.currentUserId, + courseId, + lessonId, + stepId + ) + ) + } + preference.isChecked = false + } + false } } } diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/TheoryFreeNavigation.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/TheoryFreeNavigation.kt index cf26d453..88052ee8 100644 --- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/TheoryFreeNavigation.kt +++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/TheoryFreeNavigation.kt @@ -77,12 +77,17 @@ fun AbstractStepFragment.toCurrentStep( toStep(currStep) }.devnull -fun Fragment.toLastCourseStep( +fun Fragment.toLastOrCurrCourseStep( courseId: Long, theoryRepository: TheoryRepository = get() ): Unit = scope().launch { + val currStep = theoryRepository.currentStep(courseId) val lastStep = theoryRepository.lastCourseStep(courseId) - toStep(lastStep) + toStep( + // currStep < lastStep can occur after disabling teacher mode + if (currStep < lastStep) currStep + else lastStep + ) }.devnull fun Fragment.toLastLessonStep( From 31c2a0f01b8284ddce41b25f6dcb79b599067d73 Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Sun, 22 Aug 2021 00:58:33 +0300 Subject: [PATCH 10/58] Fix android tests --- .../braillesystems/learnbraille/data/Utils.kt | 37 +++++++++++++++++++ .../data/repository/ActionsRepositoryTest.kt | 7 ++-- .../repository/MaterialsRepositoryTest.kt | 30 +++------------ 3 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 app/src/androidTest/java/com/github/braillesystems/learnbraille/data/Utils.kt diff --git a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/Utils.kt b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/Utils.kt new file mode 100644 index 00000000..24cdf5a8 --- /dev/null +++ b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/Utils.kt @@ -0,0 +1,37 @@ +package com.github.braillesystems.learnbraille.data + +import com.github.braillesystems.learnbraille.data.entities.DBid +import com.github.braillesystems.learnbraille.data.entities.User +import com.github.braillesystems.learnbraille.data.repository.PreferenceRepository +import com.github.braillesystems.learnbraille.utils.unreachable + +open class UnreachablePreferencesRepository : PreferenceRepository { + override val buzzEnabled: Boolean + get() = unreachable + override val toastsEnabled: Boolean + get() = unreachable + override val golubinaBookStepsEnabled: Boolean + get() = unreachable + override val slateStylusStepsEnabled: Boolean + get() = unreachable + override val traverseDotsInEnumerationOrder: Boolean + get() = unreachable + override val inputOnFlyCheck: Boolean + get() = unreachable + override val additionalAnnouncementsEnabled: Boolean + get() = unreachable + override val practiceUseOnlyKnownMaterials: Boolean + get() = unreachable + override val extendedAccessibilityEnabled: Boolean + get() = unreachable + override val additionalQrCodeButtonEnabled: Boolean + get() = unreachable + override val isWriteModeFirst: Boolean + get() = unreachable + override val teacherModeEnabled: Boolean + get() = unreachable + override val currentUserId: DBid + get() = unreachable + + override suspend fun getCurrentUser(): User = unreachable +} diff --git a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepositoryTest.kt b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepositoryTest.kt index 4109b78b..9bb302f4 100644 --- a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepositoryTest.kt +++ b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/ActionsRepositoryTest.kt @@ -3,12 +3,12 @@ package com.github.braillesystems.learnbraille.data.repository import androidx.room.Room import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.github.braillesystems.learnbraille.data.UnreachablePreferencesRepository import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase -import com.github.braillesystems.learnbraille.data.entities.Action -import com.github.braillesystems.learnbraille.data.entities.PracticeHintAction -import com.github.braillesystems.learnbraille.data.entities.TheoryPassStep +import com.github.braillesystems.learnbraille.data.entities.* import com.github.braillesystems.learnbraille.utils.Days import com.github.braillesystems.learnbraille.utils.minus +import com.github.braillesystems.learnbraille.utils.unreachable import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert.assertEquals @@ -42,6 +42,7 @@ class ActionsRepositoryTest { } repo = ActionsRepositoryImpl( db.actionDao, + UnreachablePreferencesRepository(), getCurrDate = { currDate }, keepActionsTime = Days(100) ) diff --git a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepositoryTest.kt b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepositoryTest.kt index 22dac0f2..c9061447 100644 --- a/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepositoryTest.kt +++ b/app/src/androidTest/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepositoryTest.kt @@ -3,13 +3,13 @@ package com.github.braillesystems.learnbraille.data.repository import androidx.room.Room import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.github.braillesystems.learnbraille.data.UnreachablePreferencesRepository import com.github.braillesystems.learnbraille.data.db.LearnBrailleDatabase import com.github.braillesystems.learnbraille.data.entities.* -import com.github.braillesystems.learnbraille.data.entities.BrailleDot.E -import com.github.braillesystems.learnbraille.data.entities.BrailleDot.F -import com.github.braillesystems.learnbraille.res.MarkerType +import com.github.braillesystems.learnbraille.data.entities.StepAnnotation +import com.github.braillesystems.learnbraille.data.entities.BrailleDot.* +import com.github.braillesystems.learnbraille.res.* import com.github.braillesystems.learnbraille.res.SymbolType -import com.github.braillesystems.learnbraille.utils.unreachable import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert.assertEquals @@ -157,29 +157,9 @@ class MaterialsRepositoryTest { repo = MaterialsRepositoryImpl( db.deckDao, db.cardDao, - object : PreferenceRepository { - override val buzzEnabled: Boolean - get() = unreachable - override val toastsEnabled: Boolean - get() = unreachable - override val golubinaBookStepsEnabled: Boolean - get() = unreachable - override val slateStylusStepsEnabled: Boolean - get() = unreachable - override val traverseDotsInEnumerationOrder: Boolean - get() = unreachable - override val inputOnFlyCheck: Boolean - get() = unreachable - override val additionalAnnouncementsEnabled: Boolean - get() = unreachable + object : UnreachablePreferencesRepository() { override val practiceUseOnlyKnownMaterials: Boolean get() = true - override val extendedAccessibilityEnabled: Boolean - get() = unreachable - override val additionalQrCodeButtonEnabled: Boolean - get() = unreachable - override val isWriteModeFirst: Boolean - get() = unreachable override val currentUserId: DBid get() = 1 From 138ff6944e373f7cb1d3de91f8f29ddc442056a2 Mon Sep 17 00:00:00 2001 From: Andrey Stoyan Date: Sun, 22 Aug 2021 01:02:50 +0300 Subject: [PATCH 11/58] Fix detekt check --- detekt-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detekt-config.yml b/detekt-config.yml index 32c66009..3cb39ad2 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -2,7 +2,7 @@ complexity: TooManyFunctions: - excludes: ['**/utils/**', '**/DotsChecker.kt'] + excludes: ['**/utils/**', '**/DotsChecker.kt', '**/TheoryRepository.kt'] LongMethod: excludes: ['**/*Fragment.kt', '**/*Application.kt', '**/LearnBrailleDatabase.kt'] ComplexMethod: From ab44fc0c5939f3b7b61e39e34e3d26a8fc321f59 Mon Sep 17 00:00:00 2001 From: zuevval Date: Tue, 24 Aug 2021 16:41:37 +0300 Subject: [PATCH 12/58] #302 enable manual workflow runs --- .github/workflows/android.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 07234ec8..97c57c1e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -9,6 +9,7 @@ on: - master - dev pull_request: + workflow_dispatch: jobs: build-and-test: # instrumental tests run on MacOS to enable hardware acceleration From 10c7dc842b5c332db288bb2888cc0416bf62f7a6 Mon Sep 17 00:00:00 2001 From: zuevval Date: Tue, 24 Aug 2021 17:03:31 +0300 Subject: [PATCH 13/58] #304 try another version of the Actions plugin --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 97c57c1e..01e7e702 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -28,7 +28,7 @@ jobs: run: ./gradlew test - name: Run instrumental tests # warning: it depends on "grant permission to gradlew" step - uses: reactivecircus/android-emulator-runner@v1 + uses: reactivecircus/android-emulator-runner@v2 with: api-level: 29 script: ./gradlew connectedCheck From 3d392a2473bab07449c101b1d7a2ae052b52e1f0 Mon Sep 17 00:00:00 2001 From: zuevval Date: Wed, 25 Aug 2021 11:35:07 +0300 Subject: [PATCH 14/58] #299 adjust teacher mode settings style --- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/styles.xml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8926fb2..3be2ff74 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -545,7 +545,7 @@ Режим преподавателя
- В режиме преподавателя доступны все уроки. Для активации потребуется ввести код преподавателя. + В режиме преподавателя доступны все уроки и не ведётся учёт статистики. Для активации потребуется ввести код преподавателя. Неверный код, попробуйте ещё раз diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 0456efb2..783a944a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -9,12 +9,18 @@ @color/colorOnPrimary @color/colorOnSecondary @color/colorBackground> + @style/AlertDialogCustom @style/Widget.MaterialComponents.Button + +