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/.github/workflows/android.yml b/.github/workflows/android.yml
index 07234ec8..01e7e702 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
@@ -27,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
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- generateDebugSources
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index dd487745..ae6e74d9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,122 +1,125 @@
-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 15
- versionName "1.2.1"
- 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 17
+ versionName "1.3.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'
+
+ // Playing musical notes
+ implementation 'com.github.braille-systems:perfectTune:bc09ec2890'
+}
+
+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/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/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/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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5a937f5c..30863e5c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,27 +1,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..41d6256b 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 {
+ PreferenceRepositoryImpl(
+ this@LearnBrailleApplication,
+ get().userDao
+ )
+ }
+ factory {
+ PreferenceRepositoryImpl(
+ this@LearnBrailleApplication,
+ get().userDao
+ )
+ }
+
+ factory {
+ ActionsRepositoryImpl(get().actionDao, get())
+ }
+ factory {
+ ActionsRepositoryImpl(get().actionDao, get())
+ }
+
+ 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..90896701 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,341 @@
-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 = 20,
+ 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,
+ MIGRATION_19_20
+ )
+ .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")
+ }
+}
+
+fun updateTheoryAndMaterials(database: SupportSQLiteDatabase) {
+ 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("New data inserted")
+}
+
+private val MIGRATION_18_19 = object : Migration(18, 19) {
+ override fun migrate(database: SupportSQLiteDatabase) {
+ Timber.i("Start 18-19 migration")
+ updateTheoryAndMaterials(database)
+ Timber.i("Finish 18-19 migration")
+ }
+}
+
+private val MIGRATION_19_20 = object : Migration(19, 20) {
+ override fun migrate(database: SupportSQLiteDatabase) {
+ Timber.i("Start 19-20 migration")
+ updateTheoryAndMaterials(database)
+ Timber.i("Finish 19-20 migration")
+ }
+}
+
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/dsl/DataStorage.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/dsl/DataStorage.kt
index 23f71f28..31655a87 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/data/dsl/DataStorage.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/dsl/DataStorage.kt
@@ -141,7 +141,7 @@ class DataBuilder(
DecksBuilder(block).side {
it.deckToPredicate.forEach { (deck, p) ->
val deckId =
- if (deck.tag == DeckTags.all) ALL_CARDS_DECK_ID
+ if (deck.tag == DeckTags.Grouping.All.tag) ALL_CARDS_DECK_ID
else decks.size + 2L
val deckMaterials = materials.filter { material -> p(material.data) }
if (deckMaterials.isNotEmpty()) {
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/StepData.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/StepData.kt
index ede280b8..25ab9b00 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/StepData.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/entities/StepData.kt
@@ -83,6 +83,20 @@ data class Input(
}
}
+typealias Phrase = List
+
+@Serializable
+data class InputPhraseLetter(
+ val phrase: Phrase,
+ val pos: Int
+) : BaseInput() {
+
+ override val brailleDots: BrailleDots
+ get() = when (val data = phrase[pos].data) {
+ is OneBrailleSymbol -> data.brailleDots
+ }
+}
+
/**
* Step prompts the user to enter dots with specific numbers.
*
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/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/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()
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepository.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepository.kt
index 79654363..cfc88872 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepository.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/MaterialsRepository.kt
@@ -1,6 +1,7 @@
package com.github.braillesystems.learnbraille.data.repository
import com.github.braillesystems.learnbraille.data.entities.*
+import com.github.braillesystems.learnbraille.res.DeckTags
data class DeckWithAvailability(
val deck: Deck,
@@ -8,12 +9,14 @@ data class DeckWithAvailability(
)
interface MaterialsRepository {
+ suspend fun deck(id: DBid): Deck?
suspend fun randomMaterialFromDeck(id: DBid): Material?
suspend fun randomKnownMaterialFromDeck(id: DBid): Material?
suspend fun allMaterialsFromDeck(id: DBid): List
suspend fun allDecks(): List
suspend fun availableDecks(): List
suspend fun allDecksWithAvailability(): List
+ suspend fun allUniqueDecks(): List
}
open class MaterialsRepositoryImpl(
@@ -22,6 +25,8 @@ open class MaterialsRepositoryImpl(
private val preferenceRepository: PreferenceRepository
) : MaterialsRepository {
+ override suspend fun deck(id: DBid): Deck? = deckDao.deck(id)
+
override suspend fun randomMaterialFromDeck(id: DBid): Material? =
cardDao.randomMaterialFromDeck(id)
@@ -44,4 +49,9 @@ open class MaterialsRepositoryImpl(
} else {
deckDao.allDecks().map { DeckWithAvailability(it, true) }
}
+
+ override suspend fun allUniqueDecks(): List {
+ val uniqueDecksTags = DeckTags.Unique.values().map { it.tag }
+ return deckDao.allDecks().filter { it.tag in uniqueDecksTags }
+ }
}
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/data/repository/TheoryRepository.kt b/app/src/main/java/com/github/braillesystems/learnbraille/data/repository/TheoryRepository.kt
index c3582605..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
@@ -87,14 +91,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/res/Data.kt b/app/src/main/java/com/github/braillesystems/learnbraille/res/Data.kt
index 578dcf10..2a53851d 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/res/Data.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/res/Data.kt
@@ -9,6 +9,34 @@ import com.github.braillesystems.learnbraille.data.entities.Symbol
import com.github.braillesystems.learnbraille.utils.contextNotNull
import com.github.braillesystems.learnbraille.utils.lazyWithContext
+val musicalNotesTypes = listOf(
+ MarkerType.NoteC,
+ MarkerType.NoteD,
+ MarkerType.NoteE,
+ MarkerType.NoteF,
+ MarkerType.NoteG,
+ MarkerType.NoteA,
+ MarkerType.NoteB
+)
+
+val otherMusicalTypes = listOf(
+ MarkerType.MusicRest8th,
+ MarkerType.MusicRest4th,
+ MarkerType.MusicRestHalf,
+ MarkerType.MusicRestFull,
+ MarkerType.OctaveMark1,
+ MarkerType.OctaveMark2,
+ MarkerType.OctaveMark3,
+ MarkerType.OctaveMark4,
+ MarkerType.OctaveMark5,
+ MarkerType.OctaveMark6,
+ MarkerType.OctaveMark7,
+ MarkerType.MusicSharp,
+ MarkerType.MusicFlat,
+ MarkerType.MusicNatural,
+ MarkerType.MusicHyphen
+)
+
val prepopulationData by data(
materials = content,
stepAnnotationNames = listOf(
@@ -46,35 +74,42 @@ val prepopulationData by data(
decks {
// All cards deck should always exist and be first in the list
- deck(DeckTags.all) { true }
+ deck(DeckTags.Grouping.All.tag) { true }
- deck(DeckTags.allWithRus) { data ->
+ deck(DeckTags.Grouping.AllWithRus.tag) { data ->
val isNative = data is Symbol
&& data.type != SymbolType.greek
&& data.type != SymbolType.latin
isNative || data !is Symbol
}
- deck(DeckTags.ruLetters) { data ->
+ deck(DeckTags.Unique.RuLetters.tag) { data ->
data is Symbol && data.type == SymbolType.ru
}
- deck(DeckTags.latinLetters) { data ->
+ deck(DeckTags.Unique.LatinLetters.tag) { data ->
data is Symbol && data.type == SymbolType.latin
}
- deck(DeckTags.greekLetters) { data ->
+ deck(DeckTags.Unique.GreekLetters.tag) { data ->
data is Symbol && data.type == SymbolType.greek
}
- deck(DeckTags.special) { data ->
+ deck(DeckTags.Unique.Special.tag) { data ->
data is Symbol && data.type == SymbolType.special
}
- deck(DeckTags.markers) { data ->
- data is MarkerSymbol
+ deck(DeckTags.Unique.Markers.tag) { data ->
+ data is MarkerSymbol && data.type !in (musicalNotesTypes + otherMusicalTypes)
+ }
+ deck(DeckTags.Unique.MusicalNotes.tag) { data ->
+ data is MarkerSymbol && data.type in musicalNotesTypes
}
- deck(DeckTags.digits) { data ->
+ deck(DeckTags.Unique.Digits.tag) { data ->
data is Symbol && data.type == SymbolType.digit
}
- deck(DeckTags.math) { data ->
+ deck(DeckTags.Unique.Math.tag) { data ->
data is Symbol && data.type == SymbolType.math
}
+ deck(DeckTags.Unique.OtherMusic.tag) { data ->
+ data is MarkerSymbol && data.type in otherMusicalTypes
+ }
+
}
}
@@ -84,31 +119,38 @@ object StepAnnotation {
}
object DeckTags {
- const val all = "all"
- const val allWithRus = "all_with_rus"
- const val ruLetters = "ru_letters"
- const val latinLetters = "latin_letters"
- const val greekLetters = "greek_letters"
- const val digits = "digits"
- const val markers = "markers"
- const val special = "special"
- const val math = "math"
+ enum class Grouping(val tag: String) {
+ All("all"),
+ AllWithRus("all_with_rus")
+ }
+
+ enum class Unique(val tag: String) {
+ RuLetters("ru_letters"),
+ LatinLetters("latin_letters"),
+ GreekLetters("greek_letters"),
+ Digits("digits"),
+ Markers("markers"),
+ Special("special"),
+ Math("math"),
+ MusicalNotes("notes"),
+ OtherMusic("other_music")
+ }
}
val Context.deckTagToName: Map by lazyWithContext {
- DeckTags.run {
- mapOf(
- all to getString(R.string.deck_name_all),
- allWithRus to getString(R.string.deck_name_all_but_foreign),
- ruLetters to getString(R.string.deck_name_ru_letters),
- latinLetters to getString(R.string.deck_name_latin_letters),
- greekLetters to getString(R.string.deck_name_greek_letters),
- digits to getString(R.string.deck_name_digits),
- markers to getString(R.string.deck_name_markers),
- special to getString(R.string.deck_name_punctuation),
- math to getString(R.string.deck_name_math)
- )
- }
+ mapOf(
+ DeckTags.Grouping.All.tag to getString(R.string.deck_name_all),
+ DeckTags.Grouping.AllWithRus.tag to getString(R.string.deck_name_all_but_foreign),
+ DeckTags.Unique.RuLetters.tag to getString(R.string.deck_name_ru_letters),
+ DeckTags.Unique.LatinLetters.tag to getString(R.string.deck_name_latin_letters),
+ DeckTags.Unique.GreekLetters.tag to getString(R.string.deck_name_greek_letters),
+ DeckTags.Unique.Digits.tag to getString(R.string.deck_name_digits),
+ DeckTags.Unique.Markers.tag to getString(R.string.deck_name_markers),
+ DeckTags.Unique.Special.tag to getString(R.string.deck_name_punctuation),
+ DeckTags.Unique.Math.tag to getString(R.string.deck_name_math),
+ DeckTags.Unique.MusicalNotes.tag to getString(R.string.deck_name_musical_notes),
+ DeckTags.Unique.OtherMusic.tag to getString(R.string.deck_other_musical_symbols)
+ )
}
val Fragment.deckTagToName
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/res/GolubinaCourse.kt b/app/src/main/java/com/github/braillesystems/learnbraille/res/GolubinaCourse.kt
index d0c51b4e..31b53479 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/res/GolubinaCourse.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/res/GolubinaCourse.kt
@@ -35,7 +35,7 @@ internal val golubinaIntroLessons by lessons {
"""
Урок $iLesson. Тренировка чтения и ввода отдельных комбинаций.
- В азбуке Брайля основой любого символа является шеститочие, в котором точки
+ В системе Брайля основой любого символа является шеститочие, в котором точки
расположены в два столбца по три точки. Каждая точка имеет определённый порядковый
номер.
@@ -176,17 +176,26 @@ internal val golubinaIntroLessons by lessons {
+Show(content.symbols.getValue('Е'))
+Input(content.symbols.getValue('Е'))
+Info("""В следующих трёх шагах нужно ввести буквы Б, А, Ц (вместе это слово БАЦ).""")
- inputChars("БАЦ")
+ inputPhraseByLetters("БАЦ")
+Info("""Теперь наберите в следующих трёх шагах, вводя букву за буквой, слово ДЕД.""")
- inputChars("ДЕД")
+ inputPhraseByLetters("ДЕД")
+Info("""Далее в следующих четырёх шагах введите слово БАБА.""")
- inputChars("БАБА")
+ inputPhraseByLetters("БАБА")
+Info(
"""Запишите на брайлевском приборе три строки: из букв Ц, Д и Е.
После этого запишите через пробел слова: БАЦ, ДЕД, БАБА.
"""
).annotate(StepAnnotation.slateStylusRequired)
+ +Info("""Отметим, что правила написания букв и других символов соответствуют
+ стандартам общеупотребительного Брайля, принятым в России, которые
+ утверждены Комиссией Центрального
+ правления Всероссийского общества слепых в 2013 году.
+ Подробнее об этом можно прочесть на сайте комиссии:
+
+ www.chtenie.spb.ru/komissia.htm
+ """
+ )
+Info(InfoInterpolation.run {
"""Урок $iLesson закончен. В следующем занятии займёмся повторением букв А, Б, Ц, Д, Е
и потренируемся писать цифры от 1 до 5, образуемые с помощью этих букв. Рекомендуем
@@ -230,9 +239,14 @@ internal val golubinaIntroLessons by lessons {
Они получаются из букв А, Б, Ц, Д, Е добавлением цифрового знака.
Например, цифра 3, как и число 3 - это цифровой знак + Ц.
Число двадцать четыре - это цифровой знак, затем буквы Б и Д.
- В уроках мы для краткости не будем всякий раз ставить цифровой знак."""
+ В следующих шагах просмотрите, а затем введите число «двенадцать тысяч
+ триста сорок пять»."""
)
- showAndInputChars("12345")
+ +Show(content.markers.getValue(MarkerType.NumberSign))
+ for (char in "12345") {
+ +Show(content.symbols.getValue(char))
+ }
+ inputNumber(12345)
+Info(
"""
На пятой сверху строчке на странице 14 пособия, под строкой с цифровым знаком,
@@ -251,7 +265,9 @@ internal val golubinaIntroLessons by lessons {
Урок $iLesson пройден! Рекомендуем самостоятельно изучить цифры и числа на странице 15
в пособии (внизу страницы).
- Следующий урок будет посвящён буквам Ф, Г и цифрам 6, 7."""
+ Также цифры можно повторять в разделе приложения "Практика".
+ В разделе "Практика" не нужно вводить перед цифрами цифровой знак,
+ но при письме обязательно ставить его перед каждым числом."""
})
}
@@ -265,7 +281,7 @@ internal val golubinaIntroLessons by lessons {
Перед прохождением нового материала повторим пройденное.
В следующих пяти шагах нужно ввести буквы Б, E, Д, А (вместе это слово БЕДА)."""
})
- inputChars("БЕДА")
+ inputPhraseByLetters("БЕДА")
+Info(
"""
Теперь познакомимся с буквами Ф и Г.
@@ -298,7 +314,7 @@ internal val golubinaIntroLessons by lessons {
"""
В следующих трёх шагах введите по буквам слово БЕГ."""
)
- inputChars("БЕГ")
+ inputPhraseByLetters("БЕГ")
+Info(
"""
Цифровой знак и буква Ф за ним обозначают арабскую цифру 6 или число 6.
@@ -307,8 +323,7 @@ internal val golubinaIntroLessons by lessons {
В следующих трёх шагах введите число ШЕСТЬДЕСЯТ СЕМЬ."""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("67")
+ inputNumber(67)
+Info(
"""Напишите слово БЕГ и число "ШЕСТДЕСЯТ СЕМЬ" при помощи брайлевского прибора."""
).annotate(StepAnnotation.slateStylusRequired)
@@ -327,16 +342,15 @@ internal val golubinaIntroLessons by lessons {
По окончании этого урока Вы узнаете букву Х и цифру 8.
Но перед изучением нового повторим пройденное. Введите по буквам слово ФЕБ."""
})
- inputChars("ФЕБ")
+ inputPhraseByLetters("ФЕБ")
+Info("""В следующих шести шагах введите по буквам слово БАГДАД.""")
- inputChars("БАГДАД")
+ inputPhraseByLetters("БАГДАД")
+Info(
"""Наберите в следующих четырёх шагах число СТО ДВАДЦАТЬ ТРИ, поставив перед ним
цифровой знак.
"""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("123")
+ inputNumber(123)
+Info(
"""
Буква Х обозначается точками 1, 2 и 5.
@@ -356,12 +370,11 @@ internal val golubinaIntroLessons by lessons {
+Input(content.symbols.getValue('8'))
slateStylusLine('Х')
+Info("""В следующих шагах введите по буквам слово ЦЕХ""")
- inputChars("ЦЕХ")
+ inputPhraseByLetters("ЦЕХ")
+Info("""Наберите восклицание АХ""")
- inputChars("АХ")
+ inputPhraseByLetters("АХ")
+Info("""Далее введите, поставив цифровой знак, число "ВОСЕМЬСОТ СЕМЬДЕСЯТ ШЕСТЬ" """)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("876")
+ inputNumber(876)
+Info(
"""Введите на брайлевском приборе слова ЦЕХ, АХ и число ВОСЕМЬСОТ СЕМЬДЕСЯТ ШЕСТЬ."""
).annotate(StepAnnotation.slateStylusRequired)
@@ -398,25 +411,24 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите буквы, составляющие слово БАГАЖ"""
)
- inputChars("БАГАЖ")
+ inputPhraseByLetters("БАГАЖ")
+Info(
"""Введите буквы, составляющие слово ЖАЖДА"""
)
- inputChars("ЖАЖДА")
+ inputPhraseByLetters("ЖАЖДА")
+Info(
"""Напомним, буква И кодируется двумя точками с номерами 2 и 4.
Введите буквы, образующие слово ГИД"""
)
- inputChars("ГИД")
+ inputPhraseByLetters("ГИД")
+Info(
"""Введите буквы, которые образуют слово ИЖИЦА"""
)
- inputChars("ИЖИЦА")
+ inputPhraseByLetters("ИЖИЦА")
+Info(
"""Введите по символам число 850"""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("850")
+ inputNumber(850)
+Info(
"""
Напишите с помощью брайлевского прибора слова, набранные до этого на экране:
@@ -447,7 +459,7 @@ internal val golubinaIntroLessons by lessons {
Перед занятием повторим пройденное. Введите по буквам слово ЕЖИХА.
"""
})
- inputChars("ЕЖИХА")
+ inputPhraseByLetters("ЕЖИХА")
+Info(
"""
Теперь перейдём к изучению материала.
@@ -472,11 +484,11 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по буквам слово КЛАД"""
)
- inputChars("КЛАД")
+ inputPhraseByLetters("КЛАД")
+Info(
"""Введите по буквам слово БЕЛКА"""
)
- inputChars("БЕЛКА")
+ inputPhraseByLetters("БЕЛКА")
+Info(
"""
Сегодня осталось изучить букву М.
@@ -498,15 +510,15 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""В следующих трёх шагах ведите по буквам слово МЕЛ"""
)
- inputChars("МЕЛ")
+ inputPhraseByLetters("МЕЛ")
+Info(
"""Введите по буквам слово МАК"""
)
- inputChars("МАК")
+ inputPhraseByLetters("МАК")
+Info(
"""Напоследок введите по буквам слово ФИАЛКА"""
)
- inputChars("ФИАЛКА")
+ inputPhraseByLetters("ФИАЛКА")
+Info(
"""
Напишите на брайлевском приборе пройденные в уроке слова:
@@ -535,7 +547,7 @@ internal val golubinaIntroLessons by lessons {
Начнём занятие с повторения. Введите по буквам слово КАМБАЛА.
"""
})
- inputChars("КАМБАЛА")
+ inputPhraseByLetters("КАМБАЛА")
+Info(
"""
Отлично, теперь познакомимся с буквой Н.
@@ -557,15 +569,15 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по буквам слово БАНАН"""
)
- inputChars("БАНАН")
+ inputPhraseByLetters("БАНАН")
+Info(
"""Введите по буквам слово ЦЕНА"""
)
- inputChars("ЦЕНА")
+ inputPhraseByLetters("ЦЕНА")
+Info(
"""Теперь введите по буквам слово БЛАНК"""
)
- inputChars("БЛАНК")
+ inputPhraseByLetters("БЛАНК")
+Info(
"""
С помощью брайлевского прибора запишите введённые ранее слова:
@@ -593,7 +605,7 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам слово НАДЕЖДА.
"""
})
- inputChars("НАДЕЖДА")
+ inputPhraseByLetters("НАДЕЖДА")
+Info(
"""
Идём дальше: теперь изучим букву 'О'.
@@ -613,7 +625,7 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Наберите по буквам слово ОБЛАКО"""
)
- inputChars("ОБЛАКО")
+ inputPhraseByLetters("ОБЛАКО")
+Info(
"""
Кратко ознакомимся со знаком 'Запятая'.
@@ -626,15 +638,15 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Наберите по символам слово ЛОДКА и поставьте в конце запятую"""
)
- inputChars("ЛОДКА,")
+ inputPhraseByLetters("ЛОДКА,")
+Info(
"""Аналогично предыдущему, введите слово КОФЕ и запятую"""
)
- inputChars("КОФЕ,")
+ inputPhraseByLetters("КОФЕ,")
+Info(
"""Напоследок введите слово ГЕОЛОГ и тоже поставьте запятую в конце"""
)
- inputChars("ГЕОЛОГ,")
+ inputPhraseByLetters("ГЕОЛОГ,")
+Info(
"""
Используя брайлевский прибор, запишите фразу:
@@ -662,7 +674,7 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам слово МОЛОКО и поставьте после него запятую.
"""
})
- inputChars("МОЛОКО,")
+ inputPhraseByLetters("МОЛОКО,")
+Info(
"""
Давайте познакомимся с буквой 'П'.
@@ -681,19 +693,19 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите отдельными символами слово ПЕНА"""
)
- inputChars("ПЕНА")
+ inputPhraseByLetters("ПЕНА")
+Info(
"""Теперь нужно набрать по буквам слово ЛАМПА"""
)
- inputChars("ЛАМПА")
+ inputPhraseByLetters("ЛАМПА")
+Info(
"""Наберите по буквам слово ПЛАН"""
)
- inputChars("ПЛАН")
+ inputPhraseByLetters("ПЛАН")
+Info(
"""И последнее в этом уроке: введите по буквам слово КАПКАН"""
)
- inputChars("КАПКАН")
+ inputPhraseByLetters("КАПКАН")
+Info(InfoInterpolation.run {
"""
Вот и пройден урок $iLesson. Следующее занятие отведено для изучения буквы Ч.
@@ -713,18 +725,17 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам слово ХЛОПОК.
"""
})
- inputChars("ХЛОПОК")
+ inputPhraseByLetters("ХЛОПОК")
+Info(
"""Введите отдельными символами число 215, поставив перед ним цифровой знак"""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("215")
+ inputNumber(215)
+Info(
"""
Переходим к изучению новой буквы: 'Ч'.
- Русская буква 'Ч' в азбуке Брайля составлена из точек 1, 2, 3, 4 и 5.
+ Русская буква 'Ч' в системе Брайля составлена из точек 1, 2, 3, 4 и 5.
Её можно получить из буквы Г, если дополнить точкой 3.
"""
)
@@ -737,19 +748,19 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по буквам слово ЧЕК"""
)
- inputChars("ЧЕК")
+ inputPhraseByLetters("ЧЕК")
+Info(
"""Наберите отдельными буквами слово ОЧКИ"""
)
- inputChars("ОЧКИ")
+ inputPhraseByLetters("ОЧКИ")
+Info(
"""Далее введите по буквам слово КОЧАН"""
)
- inputChars("КОЧАН")
+ inputPhraseByLetters("КОЧАН")
+Info(
"""Введите по буквам ещё одно, последнее слово: БОЧОНОК"""
)
- inputChars("БОЧОНОК")
+ inputPhraseByLetters("БОЧОНОК")
+Info(
"""
Запишите на брайлевском приборе изученные слова, разделяя их запятыми:
@@ -777,7 +788,7 @@ internal val golubinaIntroLessons by lessons {
Введите по символам слово ПЧЕЛА.
"""
})
- inputChars("ПЧЕЛА")
+ inputPhraseByLetters("ПЧЕЛА")
+Info(
"""
Сегодня мы изучим букву 'Р'.
@@ -796,23 +807,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Наберите последовательно, по буквам слово РЕКА"""
)
- inputChars("РЕКА")
+ inputPhraseByLetters("РЕКА")
+Info(
"""Наберите отдельными символами слово КРАН"""
)
- inputChars("КРАН")
+ inputPhraseByLetters("КРАН")
+Info(
"""Далее в четырёх шагах введите по буквам слово КРЕМ"""
)
- inputChars("КРЕМ")
+ inputPhraseByLetters("КРЕМ")
+Info(
"""Теперь введите по символам слово ФАРА"""
)
- inputChars("ФАРА")
+ inputPhraseByLetters("ФАРА")
+Info(
"""Наберите аналогичным образом последнее слово в сегодняшнем уроке: ГАРАЖ"""
)
- inputChars("ГАРАЖ")
+ inputPhraseByLetters("ГАРАЖ")
+Info(
"""
Возьмите брайлевский прибор и запишите через запятую пройденные слова:
@@ -838,12 +849,12 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам слово МАРАФОН
"""
})
- inputChars("МАРАФОН")
+ inputPhraseByLetters("МАРАФОН")
+Info(
"""
Предмет этого урока - буква 'С'.
- Буква 'С' в азбуке Брайля представлена точками 2, 3 и 4.
+ Буква 'С' в системе Брайля представлена точками 2, 3 и 4.
Точками 2 и 4, как вы помните, обозначается буква И. Буква С - это буква И,
дополненная точкой номер 3.
"""
@@ -858,23 +869,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""В следующих шагах наберите слово САЖА"""
)
- inputChars("САЖА")
+ inputPhraseByLetters("САЖА")
+Info(
"""Наберите по буквам слово СЛЕД"""
)
- inputChars("СЛЕД")
+ inputPhraseByLetters("СЛЕД")
+Info(
"""Введите буквы, образующие слово ФАСАД"""
)
- inputChars("ФАСАД")
+ inputPhraseByLetters("ФАСАД")
+Info(
"""Введите буквы, которые составляют слово ДОСКА"""
)
- inputChars("ДОСКА")
+ inputPhraseByLetters("ДОСКА")
+Info(
"""В завершение урока введите по буквам слово ЧЕСНОК"""
)
- inputChars("ЧЕСНОК")
+ inputPhraseByLetters("ЧЕСНОК")
+Info(
"""
Осталось немного поработать с брайлевским прибором. Запишите на нём слова:
@@ -900,12 +911,11 @@ internal val golubinaIntroLessons by lessons {
Введите символы, составляющие слово СОЛНЦЕ
"""
})
- inputChars("СОЛНЦЕ")
+ inputPhraseByLetters("СОЛНЦЕ")
+Info(
"""Теперь наберите число 870, сначала поставив цифровой знак"""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("870")
+ inputNumber(870)
+Info(
"""
Теперь рассмотрим букву 'Т'.
@@ -924,11 +934,11 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Далее, пожалуйста, введите слово ТЕКСТ"""
)
- inputChars("ТЕКСТ")
+ inputPhraseByLetters("ТЕКСТ")
+Info(
"""Введите по буквам слово ТОРТ"""
)
- inputChars("ТОРТ")
+ inputPhraseByLetters("ТОРТ")
+Info(
"""
С этого момента мы начинаем осваивать символы, содержащие точку 6.
@@ -948,11 +958,11 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по символам слово ПОЛ-ЛИМОНА"""
)
- inputChars("ПОЛ${Hyphen.c}ЛИМОНА")
+ inputPhraseByLetters("ПОЛ${Hyphen.c}ЛИМОНА")
+Info(
"""И последнее слово, которое нужно ввести в этом уроке — местоимение ГДЕ-ЛИБО"""
)
- inputChars("ГДЕ${Hyphen.c}ЛИБО")
+ inputPhraseByLetters("ГДЕ${Hyphen.c}ЛИБО")
+Info(
"""
Теперь рекомендуем Вам записать на брайлевском приборе изученные слова:
@@ -979,7 +989,7 @@ internal val golubinaIntroLessons by lessons {
На следующих этапах введите по буквам слово ТЕЛЕФОН
"""
})
- inputChars("ТЕЛЕФОН")
+ inputPhraseByLetters("ТЕЛЕФОН")
+Info(
"""
Начинаем знакомство с буквой У. Эта буква первая в нашем курсе содержит точку 6.
@@ -999,23 +1009,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""В следующих шагах введите, пожалуйста, слово УГОЛ"""
)
- inputChars("УГОЛ")
+ inputPhraseByLetters("УГОЛ")
+Info(
"""Наберите, последовательно вводя буквы, слово ЛУНА"""
)
- inputChars("ЛУНА")
+ inputPhraseByLetters("ЛУНА")
+Info(
"""Введите буквы, формирующие слово КРУГ"""
)
- inputChars("КРУГ")
+ inputPhraseByLetters("КРУГ")
+Info(
"""Введите буквы, которые бы составили слово ТРУД"""
)
- inputChars("ТРУД")
+ inputPhraseByLetters("ТРУД")
+Info(
"""Последнее, что нужно набрать в этом уроке — слово ЧУДЕСА"""
)
- inputChars("ЧУДЕСА")
+ inputPhraseByLetters("ЧУДЕСА")
+Info(
"""
Набранные в этом уроке слова запишем и на брайлевском приборе через запятую:
@@ -1042,7 +1052,7 @@ internal val golubinaIntroLessons by lessons {
Введите буквы, которые сложатся в слово ХУДОЖНИК
"""
})
- inputChars("ХУДОЖНИК")
+ inputPhraseByLetters("ХУДОЖНИК")
+Info(
"""
Ознакомимся с буквой Щ.
@@ -1063,23 +1073,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Вводя букву за буквой, составьте слово ЩЕГОЛ"""
)
- inputChars("ЩЕГОЛ")
+ inputPhraseByLetters("ЩЕГОЛ")
+Info(
"""Теперь наберите, пожалуйста, слово ПЛАЩ"""
)
- inputChars("ПЛАЩ")
+ inputPhraseByLetters("ПЛАЩ")
+Info(
"""Наберите отдельными буквами слово КЛЕЩ"""
)
- inputChars("КЛЕЩ")
+ inputPhraseByLetters("КЛЕЩ")
+Info(
"""Наберите буквы, которые бы составили слово ЩЕПКА"""
)
- inputChars("ЩЕПКА")
+ inputPhraseByLetters("ЩЕПКА")
+Info(
"""И последнее: введите слово ОЩУЩЕНИЕ"""
)
- inputChars("ОЩУЩЕНИЕ")
+ inputPhraseByLetters("ОЩУЩЕНИЕ")
+Info(
"""
Перед тем, как мы закончим занятие, запишите на брайлевском приборе все пройденные слова:
@@ -1105,7 +1115,7 @@ internal val golubinaIntroLessons by lessons {
Далее введите буквы слова ЩЕПОТКА
"""
})
- inputChars("ЩЕПОТКА")
+ inputPhraseByLetters("ЩЕПОТКА")
+Info(
"""
Новая буква, которую нам надо изучить - З.
@@ -1125,23 +1135,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Последовательно, по буквам введите слово ЗАКАЗ"""
)
- inputChars("ЗАКАЗ")
+ inputPhraseByLetters("ЗАКАЗ")
+Info(
"""Теперь из отдельных букв наберите слово ЗЕРНО"""
)
- inputChars("ЗЕРНО")
+ inputPhraseByLetters("ЗЕРНО")
+Info(
"""Наберите по буквам очень важное для нас слово АЗБУКА"""
)
- inputChars("АЗБУКА")
+ inputPhraseByLetters("АЗБУКА")
+Info(
"""В следующих пяти шагах введите слово АРБУЗ"""
)
- inputChars("АРБУЗ")
+ inputPhraseByLetters("АРБУЗ")
+Info(
"""И последнее, что сегодня осталось ввести: слово ЗАДАЧА"""
)
- inputChars("ЗАДАЧА")
+ inputPhraseByLetters("ЗАДАЧА")
+Info(
"""
Для закрепления материала напишите слова на брайлевском приборе:
@@ -1167,12 +1177,11 @@ internal val golubinaIntroLessons by lessons {
Пожалуйста, введите буквы, образующие слово МИМОЗА
"""
})
- inputChars("МИМОЗА")
+ inputPhraseByLetters("МИМОЗА")
+Info(
"""Также в качестве повторения введите цифровой знак, а за ним число 964"""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("964")
+ inputNumber(964)
+Info(
"""
Переходим к главному в сегодняшнем уроке: ознакомимся с буквой 'И краткое'.
@@ -1194,23 +1203,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Буква за буквой, введите точечным шрифтом слово КЛЕЙ"""
)
- inputChars("КЛЕЙ")
+ inputPhraseByLetters("КЛЕЙ")
+Info(
"""Введите отдельными символами второе слово с буквой Й: УЛЕЙ"""
)
- inputChars("УЛЕЙ")
+ inputPhraseByLetters("УЛЕЙ")
+Info(
"""Наберите, пожалуйста, по буквам слово РЕЙС"""
)
- inputChars("РЕЙС")
+ inputPhraseByLetters("РЕЙС")
+Info(
"""Осталось ещё два задания. В следующих четырёх шагах введите слово ЗНОЙ"""
)
- inputChars("ЗНОЙ")
+ inputPhraseByLetters("ЗНОЙ")
+Info(
"""Наконец, введите последнее слово в этом уроке: РАЙОН"""
)
- inputChars("РАЙОН")
+ inputPhraseByLetters("РАЙОН")
+Info(
"""
Все пройденные слова следует записать на брайлевском приборе. Напомним, это слова
@@ -1239,7 +1248,7 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам прилагательное ОСЕННИЙ
"""
})
- inputChars("ОСЕННИЙ")
+ inputPhraseByLetters("ОСЕННИЙ")
+Info(
"""
Мы готовы к изучению твёрдого знака.
@@ -1260,16 +1269,16 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по буквам слово СЪЕЗД"""
)
- inputChars("СЪЕЗД")
+ inputPhraseByLetters("СЪЕЗД")
+Info(
"""Символ за символом, наберите слово СУБЪЕКТ"""
)
- inputChars("СУБЪЕКТ")
+ inputPhraseByLetters("СУБЪЕКТ")
+Info(
"""
Теперь постараемся запомнить обозначение литературной точки.
- Знак препинания 'Литературная точка' образован в азбуке Брайля точками 2, 5 и 6.
+ Знак препинания 'Литературная точка' образован в системе Брайля точками 2, 5 и 6.
Не следует путать его с буквой Д.
"""
)
@@ -1291,7 +1300,7 @@ internal val golubinaIntroLessons by lessons {
В конце поставьте литературную точку.
"""
)
- inputChars("ЛЕНИНГРАД${Hyphen.c}ГОРОД${Hyphen.c}ГЕРОЙ.")
+ inputPhraseByLetters("ЛЕНИНГРАД${Hyphen.c}ГОРОД${Hyphen.c}ГЕРОЙ.")
+Info(
"""
Теперь запишите на брайлевском приборе то же самое предложение:
@@ -1317,7 +1326,7 @@ internal val golubinaIntroLessons by lessons {
Ради закрепления пройденного введите по символам слово ОБЪЕДИНЕНИЕ
"""
})
- inputChars("ОБЪЕДИНЕНИЕ")
+ inputPhraseByLetters("ОБЪЕДИНЕНИЕ")
+Info(
"""Также повторим специальные символы.
Введите цифровой знак, запятую, дефис и точку"""
@@ -1343,23 +1352,23 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Набирая буквы одна за одной, введите слово МЫЛО"""
)
- inputChars("МЫЛО")
+ inputPhraseByLetters("МЫЛО")
+Info(
"""Далее введите ещё одно слово по буквам: ЛЫЖИ"""
)
- inputChars("ЛЫЖИ")
+ inputPhraseByLetters("ЛЫЖИ")
+Info(
"""Теперь введите буквы, из которых складывается слово КЛЫК"""
)
- inputChars("КЛЫК")
+ inputPhraseByLetters("КЛЫК")
+Info(
"""Наберите предпоследнее слово в этом уроке - слово МУЗЫКА"""
)
- inputChars("МУЗЫКА")
+ inputPhraseByLetters("МУЗЫКА")
+Info(
"""Введите последнее слово - существительное во множественном числе: КОЗЫ"""
)
- inputChars("КОЗЫ")
+ inputPhraseByLetters("КОЗЫ")
+Info(
"""
Все изученные слова, пожалуйста, запишите и на брайлевском приборе:
@@ -1386,7 +1395,7 @@ internal val golubinaIntroLessons by lessons {
Введите, набирая отдельные буквы, прилагательное РЫЖИЙ
"""
})
- inputChars("РЫЖИЙ")
+ inputPhraseByLetters("РЫЖИЙ")
+Info(
"""
Два урока назад мы изучали твёрдый знак, а теперь освоим и мягкий знак тоже.
@@ -1408,24 +1417,24 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по буквам существительное СОЛЬ"""
)
- inputChars("СОЛЬ")
+ inputPhraseByLetters("СОЛЬ")
+Info(
"""Теперь нужно ввести слово МЕЛЬ"""
)
- inputChars("МЕЛЬ")
+ inputPhraseByLetters("МЕЛЬ")
+Info(
"""Далее, пожалуйста, составьте из букв слово КОНЬ"""
)
- inputChars("КОНЬ")
+ inputPhraseByLetters("КОНЬ")
+Info(
"""Осталось ввести два слова.
Первое - существительное во множественном числе: РУЧЬИ"""
)
- inputChars("РУЧЬИ")
+ inputPhraseByLetters("РУЧЬИ")
+Info(
"""Введите последнее в этом занятии слово: СТАЛЬ"""
)
- inputChars("СТАЛЬ")
+ inputPhraseByLetters("СТАЛЬ")
+Info(
"""
Как обычно, напишите на брайлевском приборе изученные слова:
@@ -1451,7 +1460,7 @@ internal val golubinaIntroLessons by lessons {
Введите шаг за шагом слово АПЕЛЬСИН
"""
})
- inputChars("АПЕЛЬСИН")
+ inputPhraseByLetters("АПЕЛЬСИН")
+Info(
"""
Теперь приступим к изучению буквы, которая сравнительно недавно появилась
@@ -1473,11 +1482,11 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Шаг за шагом введите слово ЁЛКА"""
)
- inputChars("ЁЛКА")
+ inputPhraseByLetters("ЁЛКА")
+Info(
"""Введите далее название имя ещё одного дерева - КЛЁН"""
)
- inputChars("КЛЁН")
+ inputPhraseByLetters("КЛЁН")
+Info(
"""
Теперь давайте посмотрим на букву Ш.
@@ -1496,19 +1505,19 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Наберите отдельными символами слово ШЁЛК"""
)
- inputChars("ШЁЛК")
+ inputPhraseByLetters("ШЁЛК")
+Info(
"""Теперь введите буквы, которые вместе складываются в слово ШЕСТ"""
)
- inputChars("ШЕСТ")
+ inputPhraseByLetters("ШЕСТ")
+Info(
"""Введите предпоследнее слово в нашем уроке, слово ШАРФ"""
)
- inputChars("ШАРФ")
+ inputPhraseByLetters("ШАРФ")
+Info(
"""Наконец, перед тем, как мы закончим урок, введите слово КОШКА"""
)
- inputChars("КОШКА")
+ inputPhraseByLetters("КОШКА")
+Info(
"""
Используя брайлевский прибор, запишите изученные в этом уроке существительные:
@@ -1536,7 +1545,7 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам слово МИШЕНЬ
"""
})
- inputChars("МИШЕНЬ")
+ inputPhraseByLetters("МИШЕНЬ")
+Info(
"""
Познакомимся с точечным составом буквы, последней в русском алфавите,
@@ -1558,25 +1567,25 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите брайлевские буквы, которые составляют слово ЯБЛОКО"""
)
- inputChars("ЯБЛОКО")
+ inputPhraseByLetters("ЯБЛОКО")
+Info(
"""В следующих пяти шагах нужно ввести слово ЯГОДА"""
)
- inputChars("ЯГОДА")
+ inputPhraseByLetters("ЯГОДА")
+Info(
"""Далее, пожалуйста, наберите отдельными буквами слово ПЯТНО"""
)
- inputChars("ПЯТНО")
+ inputPhraseByLetters("ПЯТНО")
+Info(
"""Ещё два слова нужно ввести в этом уроке.
Первое - название птицы: ЦАПЛЯ"""
)
- inputChars("ЦАПЛЯ")
+ inputPhraseByLetters("ЦАПЛЯ")
+Info(
"""Последнее слово в сегодняшнем уроке - ЗЕМЛЯ"""
)
- inputChars("ЗЕМЛЯ")
+ inputPhraseByLetters("ЗЕМЛЯ")
+Info(
"""
Запишите на брайлевском приборе изученные слова с буквой Я:
@@ -1602,7 +1611,7 @@ internal val golubinaIntroLessons by lessons {
Введите, набирая отдельные буквы, слово ПОЛЯНА и поставьте после него запятую
"""
})
- inputChars("ПОЛЯНА,")
+ inputPhraseByLetters("ПОЛЯНА,")
+Info(
"""
В этом уроке нам надо познакомиться с буквой Ю.
@@ -1623,28 +1632,28 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Наберите по символам слово с новой буквой: ЮБКА"""
)
- inputChars("ЮБКА")
+ inputPhraseByLetters("ЮБКА")
+Info(
"""В следующих четырёх шагах наберите слово УТЮГ"""
)
- inputChars("УТЮГ")
+ inputPhraseByLetters("УТЮГ")
+Info(
"""Заметьте, что буква Т и буква Ю получаются друг из друга отражением
по вертикали.
После этого шага, пожалуйста, введите по буквам слово ТЮБИК"""
)
- inputChars("ТЮБИК")
+ inputPhraseByLetters("ТЮБИК")
+Info(
"""Нам осталось выполнить два задания, после чего урок заканчивается.
Первое: введите слово ТРЮК"""
)
- inputChars("ТРЮК")
+ inputPhraseByLetters("ТРЮК")
+Info(
"""Завершающее задание в этом уроке - введите буквы слова СЮЖЕТ"""
)
- inputChars("СЮЖЕТ")
+ inputPhraseByLetters("СЮЖЕТ")
+Info(
"""
Снова возьмите брайлевский прибор и напишите пройденные слова с буквами Ю, Т:
@@ -1672,14 +1681,13 @@ internal val golubinaIntroLessons by lessons {
Введите по буквам слово ИНТЕРЕСНЫЙ
"""
})
- inputChars("ИНТЕРЕСНЫЙ")
+ inputPhraseByLetters("ИНТЕРЕСНЫЙ")
+Info(
"""Ещё перед началом урока мы чуть-чуть повторим числа. Введите цифровой знак и
за ним число 1984
"""
)
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("1984")
+ inputNumber(1984)
+Info(
"""
Давайте изучим букву 'Э'.
@@ -1701,29 +1709,29 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Вводя отдельные буквы, составьте слово ЭХО"""
)
- inputChars("ЭХО")
+ inputPhraseByLetters("ЭХО")
+Info(
"""Обратите внимание, буква 'Э' образуется зеркальным отражением буквы 'О'
вдоль вертикальной оси.
В следующих пяти шагах наберите по буквам слово ПОЭМА"""
)
- inputChars("ПОЭМА")
+ inputPhraseByLetters("ПОЭМА")
+Info(
"""Теперь мы попросим Вас ввести слово ЭКРАН"""
)
- inputChars("ЭКРАН")
+ inputPhraseByLetters("ЭКРАН")
+Info(
"""
В этом уроке ещё два задания на ввод слов.
Первое из них: составьте слово ЭПИЗОД"""
)
- inputChars("ЭПИЗОД")
+ inputPhraseByLetters("ЭПИЗОД")
+Info(
"""В последнем задании нужно ввести слово ЭСТРАДА"""
)
- inputChars("ЭСТРАДА")
+ inputPhraseByLetters("ЭСТРАДА")
+Info(
"""
Теперь запишите введённые ранее слова на брайлевском приборе:
@@ -1750,12 +1758,12 @@ internal val golubinaIntroLessons by lessons {
Пожалуйста, наберите буквы, составляющие слово ЭНЦИКЛОПЕДИЯ
"""
})
- inputChars("ЭНЦИКЛОПЕДИЯ")
+ inputPhraseByLetters("ЭНЦИКЛОПЕДИЯ")
+Info(
"""
Изучим последнюю букву, которую мы пока не рассматривали - букву 'В'.
- Буква 'В' в азбуке Брайля - это комбинация четырёх точек: 2, 4, 5 и 6. Её можно
+ Буква 'В' в системе Брайля - это комбинация четырёх точек: 2, 4, 5 и 6. Её можно
составить, написав букву 'Ж' и добавив точку номер 6 в той же клетке.
"""
)
@@ -1773,29 +1781,29 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Наберите первое слово с буквой 'В' - слово ВОР"""
)
- inputChars("ВОР")
+ inputPhraseByLetters("ВОР")
+Info(
"""Мы не случайно привели слово, содержащее буквы 'В' и 'Р'. Обратите внимание, что
каждая из них получается зеркальным отражением другой по вертикали.
Далее введите по символам слово ВРАЧ"""
)
- inputChars("ВРАЧ")
+ inputPhraseByLetters("ВРАЧ")
+Info(
"""В следующих шести шагах введите, пожалуйста, буквы слова ВОРОНА"""
)
- inputChars("ВОРОНА")
+ inputPhraseByLetters("ВОРОНА")
+Info(
"""
Нужно составить ещё два слова.
Введите по буквам слово СЕВЕР"""
)
- inputChars("СЕВЕР")
+ inputPhraseByLetters("СЕВЕР")
+Info(
"""Заключительное упражнение: наберите слово КОРОВА"""
)
- inputChars("КОРОВА")
+ inputPhraseByLetters("КОРОВА")
+Info(
"""
Не забудьте для тренировки написать на брайлевском приборе набранные слова:
@@ -1823,7 +1831,7 @@ internal val golubinaIntroLessons by lessons {
В виде разминки перед уроком введите по буквам слово ДВОЕТОЧИЕ
"""
})
- inputChars("ДВОЕТОЧИЕ")
+ inputPhraseByLetters("ДВОЕТОЧИЕ")
+Info(
"""
В рельефно-точечном алфавите точка с запятой обозначается точками с номерами 2
@@ -1848,10 +1856,10 @@ internal val golubinaIntroLessons by lessons {
В следующих шагах введите слово РАЗДЕЛЕНИЕ; и поставьте в конце точку с запятой.
"""
)
- inputChars("РАЗДЕЛЕНИЕ;")
+ inputPhraseByLetters("РАЗДЕЛЕНИЕ;")
+Info(
"""
- Двоеточие записывается в азбуке Брайля с помощью точек 2 и 5. Похоже на дефис,
+ Двоеточие записывается в системе Брайля с помощью точек 2 и 5. Похоже на дефис,
но точки расположены на ряд выше.
Далее прочтите, а затем введите этот знак.
@@ -1871,7 +1879,7 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите по буквам слово ВЫВОД: и поставьте в конце двоеточие"""
)
- inputChars("ВЫВОД:")
+ inputPhraseByLetters("ВЫВОД:")
+Info(
"""
Перепишите, используя брайлевский прибор, предложение со страницы 54 пособия:
@@ -1898,7 +1906,7 @@ internal val golubinaIntroLessons by lessons {
Перед стартом немного повторения. Введите слово "ВОСКЛИЦАНИЕ"
"""
})
- inputChars("ВОСКЛИЦАНИЕ")
+ inputPhraseByLetters("ВОСКЛИЦАНИЕ")
+Info(
"""
Вопросительный знак - точки 2 и 6. Как буква Е, но точки сдвинуты на один ряд вниз.
@@ -1920,13 +1928,13 @@ internal val golubinaIntroLessons by lessons {
+Info(
"""Введите вопрос: ГДЕ?"""
)
- inputChars("ГДЕ?")
+ inputPhraseByLetters("ГДЕ?")
+Info("""Наберите по буквам вопрос: КОГДА?""")
- inputChars("КОГДА?")
+ inputPhraseByLetters("КОГДА?")
+Info(
"""
- Восклицательный знак в азбуке Брайля - точки 2, 3 и 5. Не перепутайте с буквой Ф:
+ Восклицательный знак в системе Брайля - точки 2, 3 и 5. Не перепутайте с буквой Ф:
точки расположены похожим образом, но каждая сдвинута на ряд ниже.
"""
)
@@ -1942,7 +1950,7 @@ internal val golubinaIntroLessons by lessons {
"""
).annotate(StepAnnotation.golubinaBookRequired)
+Info("""В завершение урока введите слово с восклицательным знаком на конце: ЧУДЕСНО!""")
- inputChars("ЧУДЕСНО!")
+ inputPhraseByLetters("ЧУДЕСНО!")
+Info(InfoInterpolation.run {
"""
Поздравляем. Урок $iLesson - восклицательный и вопросительный знак - завершён.
@@ -1963,7 +1971,7 @@ internal val golubinaIntroLessons by lessons {
восклицательный знак.
"""
})
- inputChars("ВНИМАНИЕ!")
+ inputPhraseByLetters("ВНИМАНИЕ!")
+Info(
"""
Левая открывающая скобка обозначается точками с номерами 1, 2 и 6, правая - 3,
@@ -1983,7 +1991,7 @@ internal val golubinaIntroLessons by lessons {
"""
).annotate(StepAnnotation.golubinaBookRequired)
+Info("""Введите слово (ВЕРОЯТНО), заключив его в скобки""")
- inputChars("(ВЕРОЯТНО)")
+ inputPhraseByLetters("(ВЕРОЯТНО)")
+Info(
"""
Теперь изучим кавычки. Левая (открывающая) кавычка - это точки 2, 3 и 6, правая
@@ -1995,7 +2003,7 @@ internal val golubinaIntroLessons by lessons {
)
showAndInputChars("«»")
+Info("""Введите название повести Тургенева - «НАКАНУНЕ», заключив его в кавычки""")
- inputChars("«НАКАНУНЕ»")
+ inputPhraseByLetters("«НАКАНУНЕ»")
+Info(
"""
Нам осталось изучить ещё один символ в этом уроке - знак "Звёздочка".
@@ -2151,10 +2159,11 @@ internal val golubinaIntroLessons by lessons {
).annotate(StepAnnotation.golubinaBookRequired)
+Info("""В следующих шагах запишите: ДВА ПЛЮС ДВА РАВНО ЧЕТЫРЕ""")
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("2+")
- +Input(content.markers.getValue(MarkerType.NumberSign))
- inputChars("2=4")
+ inputNumber(2)
+ +Input(content.symbols.getValue('+'))
+ inputNumber(2)
+ +Input(content.symbols.getValue('='))
+ inputNumber(4)
+Info(
"""
Запишите на брайлевском приборе примеры:
@@ -2212,7 +2221,7 @@ internal val golubinaIntroLessons by lessons {
"""
)
+Input(content.markers.getValue(MarkerType.LatinCapital))
- inputChars("BRAILLE")
+ inputPhraseByLetters("BRAILLE")
+Info(InfoInterpolation.run {
"""
На этом урок $iLesson завершается. В следующем занятии аналогичным образом
@@ -2220,6 +2229,80 @@ internal val golubinaIntroLessons by lessons {
"""
})
}
+ lesson("Ноты") {
+ +Info(InfoInterpolation.run {
+ """
+ Урок $iLesson: Основы нотной грамоты.
+
+ В этом кратком уроке мы узнаем, как в брайлевской нотации записываются семь нот,
+ начиная с ДО и заканчивая СИ.
+
+ В зависимости от длительности ноты обозначаются разными шеститочечными символами.
+ Здесь приведены те, что соответствуют продолжительности одна восьмая.
+ """
+ })
+ showAndInputMarkers(musicalNotesTypes)
+ +Info(
+ """
+ Ноты длительности четвертная образуются путём добавления точки 6.
+
+ Длительности половинная -- путём добавления точки 3.
+
+ Целая нота -- путём добавления точек 3 и 6 вместе.
+ """
+ )
+ +Input(additionalContent.markers.getValue(MarkerType.NoteCQuarter))
+ +Input(additionalContent.markers.getValue(MarkerType.NoteDHalf))
+ +Input(additionalContent.markers.getValue(MarkerType.NoteEFull))
+ +Info(
+ """
+ Есть обозначения для пауз разной длительности (восьмая, четвертная, половинная
+ и целая), но они образованы не путём изменений в точках 5 и 6, а немного иначе.
+ В следующих шагах ознакомимся с этими символами.
+ """
+ )
+ showAndInputMarkers(
+ listOf(
+ MarkerType.MusicRest8th,
+ MarkerType.MusicRest4th,
+ MarkerType.MusicRestHalf,
+ MarkerType.MusicRestFull
+ )
+ )
+ +Info("""
+ Каждая строка в нотах должна начинаться с обозначения октавы. В следующих шагах
+ посмотрим на их обозначения.
+ """)
+ +Show(content.markers.getValue(MarkerType.OctaveMark1))
+ +Show(content.markers.getValue(MarkerType.OctaveMark2))
+ +Show(content.markers.getValue(MarkerType.OctaveMark3))
+ +Show(content.markers.getValue(MarkerType.OctaveMark4))
+ +Show(content.markers.getValue(MarkerType.OctaveMark5))
+ +Show(content.markers.getValue(MarkerType.OctaveMark6))
+ +Show(content.markers.getValue(MarkerType.OctaveMark7))
+ +Info("""
+ Последний набор символов, с которыми мы сегодня ознакомимся:
+
+ Знаки альтерации - диез, бемоль и бекар;
+ Музыкальный перенос - аналог обычного текстового переноса в нотах.
+ Он обозначается точкой 4, как и обозначение шестой октавы, но, поскольку ставится всегда
+ в конце строки, а не в начале, то неоднозначности не возникает.
+ """)
+ +Show(content.markers.getValue(MarkerType.MusicSharp))
+ +Show(content.markers.getValue(MarkerType.MusicFlat))
+ +Show(content.markers.getValue(MarkerType.MusicNatural))
+ +Info(InfoInterpolation.run {
+ """
+ Урок $iLesson на этом завершается.
+
+ В заключение стоит отметить:
+ Луи Брайль был музыкантом и изначально позаботился о том, чтобы его система могла
+ представить ноты любой сложности. В рельефно-точечной нотной грамоте более 60
+ различных символов; некоторые из них обозначаются несколькими шеститочиями.
+ Конечно же, мы не имеем возможности изучить их все.
+ """
+ })
+ }
lesson("Греческий алфавит") {
+Info(InfoInterpolation.run {
"""
@@ -2236,11 +2319,15 @@ internal val golubinaIntroLessons by lessons {
"""
Пособие под редакцией В. Голубиной включает и греческий алфавит. Его можно найти
на страницах 93-99. Для каждой буквы приведено её название (на русском языке),
- обозначение в азбуке Брайля (вместе с признаком заглавной буквы), а также
+ обозначение в системе Брайля (вместе с признаком заглавной буквы), а также
рельефно-графическое изображение.
"""
).annotate(StepAnnotation.golubinaBookRequired)
showAndInputChars("ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ")
+ +Info("""
+ Мы изучили греческие символы так, как они пишутся в советских и российских математических учебниках.
+ Есть и другие варианты записи греческих брайлевских букв, но в нашем кратком курсе они не рассматриваются.
+ """)
+LastInfo(InfoInterpolation.run {
"""
Урок $iLesson закончен.
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/res/Materials.kt b/app/src/main/java/com/github/braillesystems/learnbraille/res/Materials.kt
index a6a2c6f8..5b6f694b 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/res/Materials.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/res/Materials.kt
@@ -34,7 +34,32 @@ enum class MarkerType {
LatinSmall,
BoldFont,
ItalicFont,
- NumberSign
+ NumberSign,
+ NoteC,
+ NoteD,
+ NoteE,
+ NoteF,
+ NoteG,
+ NoteA,
+ NoteB,
+ NoteCQuarter,
+ NoteDHalf,
+ NoteEFull,
+ MusicRest8th,
+ MusicRest4th,
+ MusicRestHalf,
+ MusicRestFull,
+ OctaveMark1,
+ OctaveMark2,
+ OctaveMark3,
+ OctaveMark4,
+ OctaveMark5,
+ OctaveMark6,
+ OctaveMark7,
+ MusicHyphen,
+ MusicSharp,
+ MusicFlat,
+ MusicNatural
}
/**
@@ -50,6 +75,12 @@ val content by materials {
+greekLetters
+mathSigns
+ms
+ +musicalNotes
+ +otherMusicalSymbols
+}
+
+val additionalContent by materials { // for the course, but not for the browser / practice
+ +notesWithDuration
}
val knownMaterials by known(
@@ -216,6 +247,41 @@ private val ms by markers {
marker(MarkerType.NumberSign, BrailleDots(E, E, F, F, F, F))
}
+private val musicalNotes by markers {
+ marker(MarkerType.NoteC, BrailleDots(F, E, E, F, F, E))
+ marker(MarkerType.NoteD, BrailleDots(F, E, E, E, F, E))
+ marker(MarkerType.NoteE, BrailleDots(F, F, E, F, E, E))
+ marker(MarkerType.NoteF, BrailleDots(F, F, E, F, F, E))
+ marker(MarkerType.NoteG, BrailleDots(F, F, E, E, F, E))
+ marker(MarkerType.NoteA, BrailleDots(E, F, E, F, E, E))
+ marker(MarkerType.NoteB, BrailleDots(E, F, E, F, F, E))
+}
+
+private val otherMusicalSymbols by markers {
+ marker(MarkerType.MusicRest8th, BrailleDots(F, E, F, F, E, F))
+ marker(MarkerType.MusicRest4th, BrailleDots(F, F, F, E, E, F))
+ marker(MarkerType.MusicRestHalf, BrailleDots(F, E, F, E, E, F))
+ marker(MarkerType.MusicRestFull, BrailleDots(F, E, F, F, E, E))
+ marker(MarkerType.OctaveMark1, BrailleDots(E, E, E, F, E, E))
+ marker(MarkerType.OctaveMark2, BrailleDots(E, E, E, F, F, E))
+ marker(MarkerType.OctaveMark3, BrailleDots(E, E, E, F, F, F))
+ marker(MarkerType.OctaveMark4, BrailleDots(E, E, E, E, F, E))
+ marker(MarkerType.OctaveMark5, BrailleDots(E, E, E, F, E, F))
+ marker(MarkerType.OctaveMark6, BrailleDots(E, E, E, E, F, F))
+ marker(MarkerType.OctaveMark7, BrailleDots(E, E, E, E, E, F))
+ marker(MarkerType.MusicHyphen, BrailleDots(E, E, E, E, F, E))
+ marker(MarkerType.MusicSharp, brailleDots = BrailleDots(F, E, E, F, E, F))
+ marker(MarkerType.MusicFlat, brailleDots = BrailleDots(F, F, E, E, E, F))
+ marker(MarkerType.MusicNatural, brailleDots = BrailleDots(F, E, E, E, E, F))
+}
+
+
+private val notesWithDuration by markers {
+ marker(MarkerType.NoteCQuarter, BrailleDots(F, E, E, F, F, F))
+ marker(MarkerType.NoteDHalf, BrailleDots(F, E, F, E, F, E))
+ marker(MarkerType.NoteEFull, BrailleDots(F, F, F, F, E, F))
+}
+
/*
* Add here rules, how to display hints for symbols.
@@ -475,6 +541,139 @@ val Context.inputMarkerPrintRules by rules(
{
val s = getString(R.string.input_mod_num_sign)
MarkerType.NumberSign::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_c)
+ MarkerType.NoteC::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_d)
+ MarkerType.NoteD::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_e)
+ MarkerType.NoteE::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_e)
+ MarkerType.NoteE::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_f)
+ MarkerType.NoteF::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_g)
+ MarkerType.NoteG::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_a)
+ MarkerType.NoteA::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_note_b)
+ MarkerType.NoteB::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.note_title_duration_template)
+ .format(getString(R.string.input_music_note_c), getString(R.string.note_duration_4th))
+ MarkerType.NoteCQuarter::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.note_title_duration_template)
+ .format(getString(R.string.input_music_note_d), getString(R.string.note_duration_half))
+ MarkerType.NoteDHalf::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.note_title_duration_template)
+ .format(getString(R.string.input_music_note_e), getString(R.string.note_duration_full))
+ MarkerType.NoteEFull::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_rest_8th)
+ MarkerType.MusicRest8th::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_rest_4th)
+ MarkerType.MusicRest4th::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_rest_half)
+ MarkerType.MusicRestHalf::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_rest_full)
+ MarkerType.MusicRestFull::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave1)
+ MarkerType.OctaveMark1::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave2)
+ MarkerType.OctaveMark2::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave3)
+ MarkerType.OctaveMark3::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave4)
+ MarkerType.OctaveMark4::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave5)
+ MarkerType.OctaveMark5::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave6)
+ MarkerType.OctaveMark6::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_octave7)
+ MarkerType.OctaveMark7::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_hyphen)
+ MarkerType.MusicHyphen::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_sharp)
+ MarkerType.MusicSharp::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_flat)
+ MarkerType.MusicFlat::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.input_music_natural)
+ MarkerType.MusicNatural::equals to { _: MarkerType -> s }
}
)
@@ -517,5 +716,129 @@ val Context.showMarkerPrintRules by rules(
{
val s = getString(R.string.show_mod_num_sign)
MarkerType.NumberSign::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_note_c)
+ MarkerType.NoteC::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_note_d)
+ MarkerType.NoteD::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_note_e)
+ MarkerType.NoteE::equals to { _: MarkerType -> s }
+ },
+ {
+ val s = getString(R.string.show_music_note_f)
+ MarkerType.NoteF::equals to { _: MarkerType -> s }
+ },
+ {
+ val s = getString(R.string.show_music_note_g)
+ MarkerType.NoteG::equals to { _: MarkerType -> s }
+ },
+ {
+ val s = getString(R.string.show_music_note_a)
+ MarkerType.NoteA::equals to { _: MarkerType -> s }
+ },
+ {
+ val s = getString(R.string.show_music_note_b)
+ MarkerType.NoteB::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.note_title_duration_template)
+ .format(getString(R.string.show_music_note_c), getString(R.string.note_duration_4th))
+ MarkerType.NoteCQuarter::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.note_title_duration_template)
+ .format(getString(R.string.show_music_note_d), getString(R.string.note_duration_half))
+ MarkerType.NoteDHalf::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.note_title_duration_template)
+ .format(getString(R.string.show_music_note_e), getString(R.string.note_duration_full))
+ MarkerType.NoteEFull::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_rest_8th)
+ MarkerType.MusicRest8th::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_rest_4th)
+ MarkerType.MusicRest4th::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_rest_half)
+ MarkerType.MusicRestHalf::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_rest_full)
+ MarkerType.MusicRestFull::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave1)
+ MarkerType.OctaveMark1::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave2)
+ MarkerType.OctaveMark2::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave3)
+ MarkerType.OctaveMark3::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave4)
+ MarkerType.OctaveMark4::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave5)
+ MarkerType.OctaveMark5::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave6)
+ MarkerType.OctaveMark6::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_octave7)
+ MarkerType.OctaveMark7::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_hyphen)
+ MarkerType.MusicHyphen::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_sharp)
+ MarkerType.MusicSharp::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_flat)
+ MarkerType.MusicFlat::equals to { _: MarkerType -> s }
+ },
+
+ {
+ val s = getString(R.string.show_music_natural)
+ MarkerType.MusicNatural::equals to { _: MarkerType -> s }
}
)
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/res/TestCourse.kt b/app/src/main/java/com/github/braillesystems/learnbraille/res/TestCourse.kt
index 5d38c1a2..99a983b9 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/res/TestCourse.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/res/TestCourse.kt
@@ -23,7 +23,7 @@ internal val testLessons by lessons {
)
+Info(name1)
+Info(
- """В рельефной азбуке Брайля любой символ - это шеститочие.
+ """В рельефной системе Брайля любой символ - это шеститочие.
Каждая точка из шести может быть выдавлена или пропущена.
В следующем шаге все 6 точек выведены на экран."""
)
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/res/_DSL.kt b/app/src/main/java/com/github/braillesystems/learnbraille/res/_DSL.kt
index 6b1d111d..eb828e57 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/res/_DSL.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/res/_DSL.kt
@@ -2,23 +2,42 @@ package com.github.braillesystems.learnbraille.res
import com.github.braillesystems.learnbraille.data.dsl.StepsBuilder
import com.github.braillesystems.learnbraille.data.dsl.annotate
-import com.github.braillesystems.learnbraille.data.entities.Info
-import com.github.braillesystems.learnbraille.data.entities.Input
-import com.github.braillesystems.learnbraille.data.entities.Show
+import com.github.braillesystems.learnbraille.data.entities.*
fun StepsBuilder.inputChars(chars: String): Unit =
chars
.map(Char::toUpperCase)
.forEach { c -> +Input(content.symbols.getValue(c)) }
+fun StepsBuilder.inputPhraseByLetters(phrase: String) {
+ val materials = phrase
+ .map(Char::toUpperCase)
+ .map(content.symbols::getValue)
+ materials.forEachIndexed { i, _ ->
+ +InputPhraseLetter(materials, i)
+ }
+}
+
fun StepsBuilder.showAndInputChars(chars: String): Unit =
chars.map(Char::toUpperCase).forEach {
+Show(content.symbols.getValue(it))
+Input(content.symbols.getValue(it))
}
+fun StepsBuilder.showAndInputMarkers(markers: List) {
+ markers.forEach {
+ +Show(content.markers.getValue(it))
+ +Input(content.markers.getValue(it))
+ }
+}
+
fun StepsBuilder.slateStylusLine(char: Char) {
+Info(
"Запишите на брайлевском приборе строку, состоящую из одного символа: $char."
).annotate(StepAnnotation.slateStylusRequired)
}
+
+fun StepsBuilder.inputNumber(number: Int) {
+ +Input(content.markers.getValue(MarkerType.NumberSign))
+ inputChars(number.toString())
+}
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/browser/BrowserDeckFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/BrowserDeckFragment.kt
new file mode 100644
index 00000000..7e29e7bc
--- /dev/null
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/BrowserDeckFragment.kt
@@ -0,0 +1,80 @@
+package com.github.braillesystems.learnbraille.ui.screens.browser
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.lifecycleScope
+import com.github.braillesystems.learnbraille.R
+import com.github.braillesystems.learnbraille.data.entities.MarkerSymbol
+import com.github.braillesystems.learnbraille.data.entities.Material
+import com.github.braillesystems.learnbraille.data.entities.Symbol
+import com.github.braillesystems.learnbraille.data.entities.spelling
+import com.github.braillesystems.learnbraille.data.repository.BrowserRepository
+import com.github.braillesystems.learnbraille.databinding.FragmentBrowserBinding
+import com.github.braillesystems.learnbraille.res.deckTagToName
+import com.github.braillesystems.learnbraille.res.showMarkerPrintRules
+import com.github.braillesystems.learnbraille.res.showSymbolPrintRules
+import com.github.braillesystems.learnbraille.ui.screens.AbstractFragmentWithHelp
+import com.github.braillesystems.learnbraille.utils.getValue
+import com.github.braillesystems.learnbraille.utils.navigate
+import com.github.braillesystems.learnbraille.utils.stringify
+import com.github.braillesystems.learnbraille.utils.title
+import kotlinx.coroutines.launch
+import org.koin.android.ext.android.inject
+
+class BrowserDeckFragment : AbstractFragmentWithHelp(R.string.browser_deck_help) {
+
+ private val browserRepository: BrowserRepository by inject()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = DataBindingUtil.inflate(
+ inflater,
+ R.layout.fragment_browser,
+ container,
+ false
+ ).ini().also { binding ->
+ lifecycleScope.launch {
+ val deckId = browserRepository.currentDeckId
+ browserRepository.deck(deckId)?.also { title = deckTagToName.getValue(it.tag) }
+ val materials =
+ browserRepository.allMaterialsFromDeck(deckId).map { DeckOrMaterial(it) }
+ binding.materialsList.adapter = BrowserListAdapter(materials) { item ->
+ this.item = item
+ item.material?.apply {
+ materialText.text = when (data) {
+ is Symbol -> getString(R.string.browser_represent_template).format(
+ showSymbolPrintRules.getValue(data.char),
+ data.brailleDots.spelling
+ )
+ is MarkerSymbol -> getString(R.string.browser_represent_template).format(
+ showMarkerPrintRules.getValue(data.type),
+ data.brailleDots.spelling
+ )
+ }
+ }
+ clickListener = object : BrowserItemListener {
+ override fun onClick(item: DeckOrMaterial) {
+ item.material?.apply {
+ val arg = stringify(Material.serializer(), this)
+ when (data) {
+ is Symbol -> navigate(
+ BrowserDeckFragmentDirections
+ .actionBrowserDeckFragmentToSymbolViewFragment(arg)
+ )
+ is MarkerSymbol -> navigate(
+ BrowserDeckFragmentDirections
+ .actionBrowserDeckFragmentToMarkerViewFragment(arg)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }.root
+}
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/BrowserFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/BrowserFragment.kt
index eb8eaa15..369bc919 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/BrowserFragment.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/BrowserFragment.kt
@@ -7,25 +7,20 @@ import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.github.braillesystems.learnbraille.R
-import com.github.braillesystems.learnbraille.data.entities.MarkerSymbol
+import com.github.braillesystems.learnbraille.data.entities.Deck
import com.github.braillesystems.learnbraille.data.entities.Material
-import com.github.braillesystems.learnbraille.data.entities.Symbol
-import com.github.braillesystems.learnbraille.data.entities.spelling
-import com.github.braillesystems.learnbraille.data.repository.BrowserRepository
+import com.github.braillesystems.learnbraille.data.repository.MutableBrowserRepository
import com.github.braillesystems.learnbraille.databinding.BrowserListItemBinding
import com.github.braillesystems.learnbraille.databinding.FragmentBrowserBinding
-import com.github.braillesystems.learnbraille.res.showMarkerPrintRules
-import com.github.braillesystems.learnbraille.res.showSymbolPrintRules
+import com.github.braillesystems.learnbraille.res.deckTagToName
import com.github.braillesystems.learnbraille.ui.screens.AbstractFragmentWithHelp
-import com.github.braillesystems.learnbraille.utils.getValue
import com.github.braillesystems.learnbraille.utils.navigate
-import com.github.braillesystems.learnbraille.utils.stringify
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
class BrowserFragment : AbstractFragmentWithHelp(R.string.browser_help) {
- private val browserRepository: BrowserRepository by inject()
+ private val browserRepository: MutableBrowserRepository by inject()
override fun onCreateView(
inflater: LayoutInflater,
@@ -39,45 +34,44 @@ class BrowserFragment : AbstractFragmentWithHelp(R.string.browser_help) {
).ini().also { binding ->
lifecycleScope.launch {
- val deckId = browserRepository.currentDeckId
- val materials = browserRepository.allMaterialsFromDeck(deckId)
- val listener = object : BrowserItemListener {
- override fun onClick(item: Material) {
- val arg = stringify(Material.serializer(), item)
- when (item.data) {
- is Symbol -> navigate(
- BrowserFragmentDirections
- .actionBrowserFragmentToSymbolViewFragment(arg)
- )
- is MarkerSymbol -> navigate(
- BrowserFragmentDirections
- .actionBrowserFragmentToMarkerViewFragment(arg)
- )
- }
- }
- }
- binding.materialsList.adapter = BrowserListAdapter(materials) { item ->
+ val decks = browserRepository.allUniqueDecks().map { DeckOrMaterial(it) }
+ binding.materialsList.adapter = BrowserListAdapter(decks) { item ->
this.item = item
- materialText.text = when (item.data) {
- is Symbol -> getString(R.string.browser_represent_template).format(
- showSymbolPrintRules.getValue(item.data.char),
- item.data.brailleDots.spelling
- )
- is MarkerSymbol -> getString(R.string.browser_represent_template).format(
- showMarkerPrintRules.getValue(item.data.type),
- item.data.brailleDots.spelling
- )
+ item.deck?.apply {
+ materialText.text = deckTagToName.getValue(tag)
+ }
+ clickListener = object : BrowserItemListener {
+ override fun onClick(item: DeckOrMaterial) {
+ item.deck?.apply {
+ // TODO Pass deck id to the BrowserDeckFragment via the fragment arguments
+ // TODO instead of the global persistent state.
+ browserRepository.currentDeckId = id
+ navigate(BrowserFragmentDirections.actionBrowserFragmentToBrowserDeckFragment())
+ }
+ }
}
- clickListener = listener
}
}
}.root
}
-private class BrowserListAdapter(
- private val materials: List,
- private val bind: BrowserListItemBinding.(Material) -> Unit
+class DeckOrMaterial {
+ var material: Material? = null
+ var deck: Deck? = null
+
+ constructor(material: Material) {
+ this.material = material
+ }
+
+ constructor(deck: Deck) {
+ this.deck = deck
+ }
+}
+
+class BrowserListAdapter(
+ private val items: List,
+ private val bind: BrowserListItemBinding.(T) -> Unit
) : RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
@@ -89,18 +83,18 @@ private class BrowserListAdapter(
)
)
- override fun getItemCount(): Int = materials.size
+ override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: BrowserItemViewHolder, position: Int) {
- val item = materials[position]
+ val item = items[position]
holder.binding.bind(item)
}
}
-private class BrowserItemViewHolder(
+class BrowserItemViewHolder(
val binding: BrowserListItemBinding
) : RecyclerView.ViewHolder(binding.root)
interface BrowserItemListener {
- fun onClick(item: Material)
+ fun onClick(item: DeckOrMaterial)
}
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/MarkerViewFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/MarkerViewFragment.kt
index 3e1a82a6..007cef08 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/MarkerViewFragment.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/browser/MarkerViewFragment.kt
@@ -1,15 +1,21 @@
package com.github.braillesystems.learnbraille.ui.screens.browser
+import android.app.AlertDialog
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.widget.Button
+import android.widget.RadioButton
+import android.widget.RadioGroup
import android.widget.TextView
+import androidx.core.view.setPadding
import androidx.databinding.DataBindingUtil
import com.github.braillesystems.learnbraille.R
-import com.github.braillesystems.learnbraille.data.entities.MarkerSymbol
-import com.github.braillesystems.learnbraille.data.entities.Material
+import com.github.braillesystems.learnbraille.data.entities.*
import com.github.braillesystems.learnbraille.databinding.FragmentMarkerViewBinding
+import com.github.braillesystems.learnbraille.res.MarkerType
+import com.github.braillesystems.learnbraille.res.musicalNotesTypes
import com.github.braillesystems.learnbraille.res.showMarkerPrintRules
import com.github.braillesystems.learnbraille.ui.screens.AbstractFragmentWithHelp
import com.github.braillesystems.learnbraille.ui.screens.BrailleDotsInfo
@@ -18,9 +24,79 @@ import com.github.braillesystems.learnbraille.ui.views.BrailleDotsViewMode
import com.github.braillesystems.learnbraille.ui.views.display
import com.github.braillesystems.learnbraille.ui.views.dotsState
import com.github.braillesystems.learnbraille.utils.*
+import com.karlotoy.perfectune.instance.PerfectTune
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+enum class NoteDuration(
+ val titleStrId: Int,
+ val dot3: Boolean,
+ val dot6: Boolean,
+ val valueMillis: Long
+) {
+ EIGHTH(titleStrId = R.string.note_duration_8th, dot3 = false, dot6 = false, valueMillis = 125),
+ QUARTER(titleStrId = R.string.note_duration_4th, dot3 = false, dot6 = true, valueMillis = 250),
+ HALF(titleStrId = R.string.note_duration_half, dot3 = true, dot6 = false, valueMillis = 500),
+ FULL(titleStrId = R.string.note_duration_full, dot3 = true, dot6 = true, valueMillis = 1000);
+
+ /// Converts a note of any duration in Braille to the note of the current duration
+ fun modifiedNote(note: BrailleDots): BrailleDots =
+ note.copy(b3 = BrailleDot.valueOf(dot3), b6 = BrailleDot.valueOf(dot6))
+}
+
+private val noteToFreq = mapOf(
+ MarkerType.NoteC to 261.63, // C4 https://pages.mtu.edu/~suits/notefreqs.html
+ MarkerType.NoteD to 293.66,
+ MarkerType.NoteE to 329.63,
+ MarkerType.NoteF to 349.23,
+ MarkerType.NoteG to 392.00,
+ MarkerType.NoteA to 440.00,
+ MarkerType.NoteB to 493.88
+)
class MarkerViewFragment : AbstractFragmentWithHelp(R.string.browser_marker_view_help) {
+ private fun chooseNoteDurationDialog(
+ brailleDots: BrailleDots,
+ previousDuration: NoteDuration,
+ block: (NoteDuration) -> Unit
+ ) {
+ val input = RadioGroup(context).apply {
+ setPadding(resources.getDimension(R.dimen.margin_text).toInt())
+ }
+ var noteDuration = previousDuration
+ val durationToButtonsMap: MutableMap = mutableMapOf()
+ NoteDuration.values().forEach { duration ->
+ input.addView(RadioButton(context).apply {
+ text = getString(R.string.browser_represent_template).format(
+ getString(duration.titleStrId),
+ duration.modifiedNote(brailleDots).spelling
+ )
+ textSize = resources.getDimension(R.dimen.dialog_items_text_size)
+ durationToButtonsMap[duration] = this
+ })
+ }
+
+ val dialog = AlertDialog.Builder(context)
+ .setTitle(getString(R.string.browser_note_duration_dialog_title))
+ .setView(input)
+ .create()
+
+ durationToButtonsMap.forEach {
+ val duration = it.key
+ val button = it.value
+ if (it.key == noteDuration) {
+ input.check(button.id)
+ }
+ button.setOnClickListener {
+ noteDuration = duration
+ block(noteDuration)
+ dialog.cancel()
+ }
+ }
+ dialog.show()
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -34,6 +110,8 @@ class MarkerViewFragment : AbstractFragmentWithHelp(R.string.browser_marker_view
object : FragmentBinding {
override val textView: TextView? = this@ini.infoTextView
override val rightButton: Button? = this@ini.flipButton
+ override val leftButton: Button? = this@ini.durationButton
+ override val rightMiddleButton: Button? = this@ini.playButton
override val brailleDotsInfo: BrailleDotsInfo? = this@ini.run {
BrailleDotsInfo(brailleDots, BrailleDotsViewMode.Reading, infoTextView, flipButton)
}
@@ -43,13 +121,45 @@ class MarkerViewFragment : AbstractFragmentWithHelp(R.string.browser_marker_view
val m: Material = parse(Material.serializer(), getFragmentStringArg("material"))
require(m.data is MarkerSymbol)
- val text = showMarkerPrintRules.getValue(m.data.type)
+ val baseText = showMarkerPrintRules.getValue(m.data.type)
+ var text = baseText
+
+ var dots: BrailleDots = m.data.brailleDots
+ if (m.data.type in musicalNotesTypes) {
+ var noteDuration = NoteDuration.EIGHTH
+ val noteDurationTemplate = getString(R.string.note_title_duration_template)
+ text = noteDurationTemplate.format(text, getString(noteDuration.titleStrId))
+ durationButton.visibility = View.VISIBLE
+ durationButton.setOnClickListener {
+ chooseNoteDurationDialog(brailleDots = dots, previousDuration = noteDuration) {
+ noteDuration = it
+ dots = it.modifiedNote(m.data.brailleDots)
+ brailleDots.dotsState.display(dots)
+ text = noteDurationTemplate.format(baseText, getString(it.titleStrId))
+ infoTextView.text = text
+ checkedAnnounce(text)
+ }
+ }
+
+ val defaultFrequency = 100.0
+ val perfectTune = PerfectTune()
+ perfectTune.tuneFreq = noteToFreq[m.data.type] ?: defaultFrequency
+ perfectTune.tuneAmplitude = 60000
+ playButton.setOnClickListener {
+ scope().launch {
+ perfectTune.playTune()
+ delay(noteDuration.valueMillis)
+ perfectTune.stopTune()
+ }
+ }
+ playButton.visibility = View.VISIBLE
+ }
+
infoTextView.text = text
checkedAnnounce(text)
- brailleDots.dotsState.display(m.data.brailleDots)
- flipButton.setOnClickListener {
- brailleDots.reflect().display(m.data.brailleDots)
- }
+
+ brailleDots.dotsState.display(dots)
+ flipButton.setOnClickListener { brailleDots.reflect().display(dots) }
}.root
}
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..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
@@ -1,162 +1,166 @@
-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.toLastOrCurrCourseStep
+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 {
+ toLastOrCurrCourseStep(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)
+ })
+
+ 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 {
+ 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..57a4ad35 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,198 @@
-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.res.musicalNotesTypes
+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
+ val baseDescription = inputMarkerPrintRules[it.type]
+ binding.markerDescription.text = if (it.type in musicalNotesTypes) {
+ getString(R.string.note_title_duration_template)
+ .format(baseDescription, getString(R.string.note_duration_8th))
+ } else baseDescription
+
+ 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..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
@@ -1,11 +1,102 @@
-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.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)
+
+ 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)
+
+ val saveCurrStepPreference =
+ "save_curr_step_teacher_mode_${preferenceRepository.currentUserId}"
+ 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")
+ 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 {
+ 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
+ }
+ }
+ }
+
+ 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/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..dba5e42f 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
@@ -16,6 +16,7 @@ import com.github.braillesystems.learnbraille.utils.*
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
import timber.log.Timber
+import java.security.InvalidParameterException
fun getAction(step: Step): NavDirections =
stringify(Step.serializer(), step).let { arg ->
@@ -33,9 +34,17 @@ fun getAction(step: Step): NavDirections =
is Symbol -> MenuFragmentDirections.actionGlobalShowSymbolFragment(arg)
is MarkerSymbol -> MenuFragmentDirections.actionGlobalShowMarkerFragment(arg)
}
+ is InputPhraseLetter -> inputPhraseNavigation(step.data.phrase, step.data.pos, arg)
}
}
+fun inputPhraseNavigation(phrase: Phrase, pos: Int, destination: String): NavDirections =
+ if (phrase[pos].data is Symbol) {
+ MenuFragmentDirections.actionGlobalInputPhraseSymbolFragment(destination)
+ } else {
+ throw InvalidParameterException("Only symbols are supported in InputPhraseLetter")
+ }
+
fun AbstractStepFragment.getStepArg(): Step =
parse(Step.serializer(), getFragmentStringArg(stepArgName))
@@ -77,12 +86,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(
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/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/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/AbstractInfoStepFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/AbstractInfoStepFragment.kt
index 60c05a0e..fc66e578 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/AbstractInfoStepFragment.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/AbstractInfoStepFragment.kt
@@ -5,6 +5,7 @@ import com.github.braillesystems.learnbraille.data.entities.BaseInfo
import com.github.braillesystems.learnbraille.ui.screens.HelpMsgId
import com.github.braillesystems.learnbraille.ui.screens.theory.steps.AbstractStepFragment
import com.github.braillesystems.learnbraille.utils.checkedAnnounce
+import com.github.braillesystems.learnbraille.utils.reduceWhitespaces
abstract class AbstractInfoStepFragment(helpMsgId: HelpMsgId) : AbstractStepFragment(helpMsgId) {
@@ -12,6 +13,6 @@ abstract class AbstractInfoStepFragment(helpMsgId: HelpMsgId) : AbstractStepFrag
val data = step.data
require(data is BaseInfo)
stepBinding.textView?.text = data.text.parseAsHtml()
- checkedAnnounce(data.text)
+ checkedAnnounce(data.text.reduceWhitespaces())
}
}
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/InfoFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/InfoFragment.kt
index 711b7a0e..13ef0e2b 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/InfoFragment.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/info/InfoFragment.kt
@@ -1,6 +1,7 @@
package com.github.braillesystems.learnbraille.ui.screens.theory.steps.info
import android.os.Bundle
+import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Button
@@ -29,5 +30,7 @@ class InfoFragment : AbstractInfoStepFragment(R.string.lessons_help_info) {
override val nextButton: Button? = this@iniStep.nextButton
override val textView: TextView? = this@iniStep.infoTextView
}
+ }.apply {
+ infoTextView.movementMethod = LinkMovementMethod.getInstance()
}.root
}
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(
diff --git a/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/InputPhraseSymbolFragment.kt b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/InputPhraseSymbolFragment.kt
new file mode 100644
index 00000000..aefbd35c
--- /dev/null
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/ui/screens/theory/steps/input/InputPhraseSymbolFragment.kt
@@ -0,0 +1,86 @@
+package com.github.braillesystems.learnbraille.ui.screens.theory.steps.input
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.databinding.DataBindingUtil
+import com.github.braillesystems.learnbraille.R
+import com.github.braillesystems.learnbraille.data.entities.InputPhraseLetter
+import com.github.braillesystems.learnbraille.data.entities.Symbol
+import com.github.braillesystems.learnbraille.databinding.FragmentLessonsInputPhraseSymbolBinding
+import com.github.braillesystems.learnbraille.res.inputSymbolPrintRules
+import com.github.braillesystems.learnbraille.ui.screens.BrailleDotsInfo
+import com.github.braillesystems.learnbraille.ui.screens.theory.steps.StepBinding
+import com.github.braillesystems.learnbraille.ui.views.BrailleDotsViewMode
+import com.github.braillesystems.learnbraille.utils.checkedAnnounce
+import com.github.braillesystems.learnbraille.utils.getValue
+
+class InputPhraseSymbolFragment : AbstractInputStepFragment(R.string.lessons_help_input_symbol) {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = DataBindingUtil.inflate(
+ inflater,
+ R.layout.fragment_lessons_input_phrase_symbol,
+ container,
+ false
+ ).iniStep(
+ titleId = R.string.lessons_title_input_symbol
+ ) {
+ object : StepBinding {
+ override val prevButton: Button? = this@iniStep.prevButton
+ override val nextButton: Button? = this@iniStep.nextButton
+ override val flipButton: Button? = this@iniStep.flipButton
+ override val hintButton: Button? = this@iniStep.hintButton
+ override val brailleDotsInfo: BrailleDotsInfo? = this@iniStep.run {
+ BrailleDotsInfo(
+ brailleDots,
+ if (preferenceRepository.isWriteModeFirst) BrailleDotsViewMode.Writing
+ else BrailleDotsViewMode.Reading,
+ prevButton, flipButton
+ )
+ }
+ }
+ }.apply {
+ val stepData = step.data
+ require(stepData is InputPhraseLetter)
+ val symbols: List = stepData.phrase.mapNotNull { it.data as? Symbol }
+ val text: String = symbols.map { it.char }.joinToString(separator = "")
+
+ val prevText = text.substring(0, stepData.pos)
+ val currentSymbol = symbols[stepData.pos]
+ val nextText = text.substring(stepData.pos + 1, symbols.size)
+
+ prevLetters.text = prevText
+ currentLetter.text = currentSymbol.char.toString()
+ nextLetters.text = nextText
+ letter.letter = currentSymbol.char
+
+ val description = taskDescription(
+ currentChar = currentSymbol.char,
+ prevText = prevText,
+ nextText = nextText
+ )
+
+ letter.contentDescription = description
+ checkedAnnounce(description)
+
+ inputViewModel = viewModel
+ lifecycleOwner = this@InputPhraseSymbolFragment
+
+ }.root
+
+ private fun taskDescription(currentChar: Char, prevText: String, nextText: String): String {
+ val fullText = prevText + currentChar + nextText
+ var enterPrompt = getString(
+ if (fullText.contains(Regex("[ !?.,:;]"))) R.string.input_phrase_template
+ else R.string.input_word_template
+ ).format(fullText)
+ if (prevText.isNotEmpty()) enterPrompt += getString(R.string.input_phrase_complete_template)
+ .format(prevText)
+ return enterPrompt + inputSymbolPrintRules.getValue(currentChar)
+ }
+}
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/java/com/github/braillesystems/learnbraille/utils/_Kotlin.kt b/app/src/main/java/com/github/braillesystems/learnbraille/utils/_Kotlin.kt
index 257cb37e..9774b77e 100644
--- a/app/src/main/java/com/github/braillesystems/learnbraille/utils/_Kotlin.kt
+++ b/app/src/main/java/com/github/braillesystems/learnbraille/utils/_Kotlin.kt
@@ -46,6 +46,8 @@ operator fun Pair.compareTo(other: Pair): Int
fun String.removeHtmlMarkup() = Regex("""<[^>]*>|&""").replace(this, "")
+fun String.reduceWhitespaces() = Regex(" +|\n").replace(this, " ")
+
inline fun runIf(cond: Boolean, block: () -> Unit) {
if (cond) block()
}
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_baseline_timer_white.xml b/app/src/main/res/drawable/ic_baseline_timer_white.xml
new file mode 100644
index 00000000..28d60aa1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_timer_white.xml
@@ -0,0 +1,5 @@
+
+
+
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/ic_play_circle_outline.xml b/app/src/main/res/drawable/ic_play_circle_outline.xml
new file mode 100644
index 00000000..c4f8875e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_play_circle_outline.xml
@@ -0,0 +1,5 @@
+
+
+
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/top_bottom_borders.xml b/app/src/main/res/drawable/top_bottom_borders.xml
new file mode 100644
index 00000000..2a3d63c7
--- /dev/null
+++ b/app/src/main/res/drawable/top_bottom_borders.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
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/browser_list_item.xml b/app/src/main/res/layout/browser_list_item.xml
index 8b3b93db..4b84f47a 100644
--- a/app/src/main/res/layout/browser_list_item.xml
+++ b/app/src/main/res/layout/browser_list_item.xml
@@ -6,7 +6,7 @@
+ type="com.github.braillesystems.learnbraille.ui.screens.browser.DeckOrMaterial" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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_lessons_input_dots.xml b/app/src/main/res/layout/fragment_lessons_input_dots.xml
index 464140f4..d9ce6739 100644
--- a/app/src/main/res/layout/fragment_lessons_input_dots.xml
+++ b/app/src/main/res/layout/fragment_lessons_input_dots.xml
@@ -16,11 +16,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_lessons_show_dots.xml b/app/src/main/res/layout/fragment_lessons_show_dots.xml
index fe99f147..a6f6339f 100644
--- a/app/src/main/res/layout/fragment_lessons_show_dots.xml
+++ b/app/src/main/res/layout/fragment_lessons_show_dots.xml
@@ -9,11 +9,7 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
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..8f68d8f7 100644
--- a/app/src/main/res/navigation/navigation.xml
+++ b/app/src/main/res/navigation/navigation.xml
@@ -1,248 +1,272 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ab/strings.xml b/app/src/main/res/values-ab/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-ab/strings.xml
+++ b/app/src/main/res/values-ab/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-ba/strings.xml b/app/src/main/res/values-ba/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-ba/strings.xml
+++ b/app/src/main/res/values-ba/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-bs/strings.xml
+++ b/app/src/main/res/values-bs/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-bua/strings.xml b/app/src/main/res/values-bua/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-bua/strings.xml
+++ b/app/src/main/res/values-bua/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-cv/strings.xml b/app/src/main/res/values-cv/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-cv/strings.xml
+++ b/app/src/main/res/values-cv/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-kk/strings.xml
+++ b/app/src/main/res/values-kk/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-kv/strings.xml b/app/src/main/res/values-kv/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-kv/strings.xml
+++ b/app/src/main/res/values-kv/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-ky/strings.xml
+++ b/app/src/main/res/values-ky/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-mn/strings.xml b/app/src/main/res/values-mn/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-mn/strings.xml
+++ b/app/src/main/res/values-mn/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-os/strings.xml b/app/src/main/res/values-os/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-os/strings.xml
+++ b/app/src/main/res/values-os/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index becaea6d..2c115b37 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -1,5 +1,5 @@
- Азбука Брайля
+ Учим Брайль
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
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-tg/strings.xml b/app/src/main/res/values-tg/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-tg/strings.xml
+++ b/app/src/main/res/values-tg/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-tk/strings.xml b/app/src/main/res/values-tk/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-tk/strings.xml
+++ b/app/src/main/res/values-tk/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-tt/strings.xml b/app/src/main/res/values-tt/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-tt/strings.xml
+++ b/app/src/main/res/values-tt/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ No newline at end of file
diff --git a/app/src/main/res/values-uz/strings.xml b/app/src/main/res/values-uz/strings.xml
index 2e320d3e..92ecba7e 100644
--- a/app/src/main/res/values-uz/strings.xml
+++ b/app/src/main/res/values-uz/strings.xml
@@ -1,4 +1,4 @@
- Азбука Брайля
+ Учим Брайль
\ 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..c4b4abf6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,10 +1,11 @@
-
-
- #263238
- #9e3d9a
- #ffffff
- #ffffff
- #ffffff
- #000000
- #504c51
-
+
+
+ #263238
+ #9e3d9a
+ #ffffff
+ #ffffff
+ #ffffff
+ #000000
+ #504c51
+ #ffc4ee
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index bd2407b8..a3413578 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -29,12 +29,7 @@
270dp0dp18sp
- 0.0
- 394dp175dp
- 0.5
- 10dp
- 0.2170sp5dp
@@ -51,4 +46,6 @@
8dp20dp
-
\ No newline at end of file
+ 7.5sp
+
+
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..b35fb5bd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,567 +1,671 @@
-
-
-
-
-
-
- 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. Меню
- Меню
-
- Загружаем базу данных. Попробуйте ещё раз!
-
- Ошибка. Пустой результат сканирования
-
-
- Обучение системе Луи Брайля: главное меню.
- &