├── app
├── .gitignore
├── src
│ ├── .DS_Store
│ ├── main
│ │ ├── .DS_Store
│ │ ├── res
│ │ │ ├── .DS_Store
│ │ │ ├── values
│ │ │ │ ├── booleans.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── integers.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values-sw600dp
│ │ │ │ ├── booleans.xml
│ │ │ │ ├── arrays.xml
│ │ │ │ └── integers.xml
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values-de
│ │ │ │ ├── integers.xml
│ │ │ │ └── arrays.xml
│ │ │ ├── values-it
│ │ │ │ ├── integers.xml
│ │ │ │ └── arrays.xml
│ │ │ ├── values-de-land
│ │ │ │ ├── integers.xml
│ │ │ │ └── arrays.xml
│ │ │ ├── values-de-sw600dp
│ │ │ │ ├── integers.xml
│ │ │ │ └── arrays.xml
│ │ │ ├── values-it-land
│ │ │ │ ├── integers.xml
│ │ │ │ └── arrays.xml
│ │ │ ├── values-de-sw600dp-land
│ │ │ │ └── integers.xml
│ │ │ ├── drawable
│ │ │ │ ├── keyboard_speak_button_icon.xml
│ │ │ │ ├── ic_save.xml
│ │ │ │ ├── saved_phrase_success_background.xml
│ │ │ │ ├── add_40dp.xml
│ │ │ │ ├── button_background.xml
│ │ │ │ ├── edit_40dp.xml
│ │ │ │ ├── error_background.xml
│ │ │ │ ├── save_40dp.xml
│ │ │ │ ├── button_speaker.xml
│ │ │ │ ├── close_action_button_icon.xml
│ │ │ │ ├── presets_action_button_icon.xml
│ │ │ │ ├── arrow_back_40dp.xml
│ │ │ │ ├── button_selected_background.xml
│ │ │ │ ├── category_group_background.xml
│ │ │ │ ├── decrease_40dp.xml
│ │ │ │ ├── keyboard_delete_button_icon.xml
│ │ │ │ ├── pointer_background.xml
│ │ │ │ ├── button_background_remove_category.xml
│ │ │ │ ├── keyboard_action_button_icon.xml
│ │ │ │ ├── keyboard_space_button_icon.xml
│ │ │ │ ├── radio_button_default_background.xml
│ │ │ │ ├── splash_background.xml
│ │ │ │ ├── button_background_disabled.xml
│ │ │ │ ├── category_back_button_icon.xml
│ │ │ │ ├── ic_resume.xml
│ │ │ │ ├── keyboard_backspace_button_icon.xml
│ │ │ │ ├── settings_action_button_icon.xml
│ │ │ │ ├── settings_group_background.xml
│ │ │ │ ├── category_forward_button_icon.xml
│ │ │ │ ├── ic_decrease_40dp.xml
│ │ │ │ ├── ic_space_bar_56dp.xml
│ │ │ │ ├── ic_space_dark_blue.xml
│ │ │ │ ├── ic_decrease_dark_blue_40dp.xml
│ │ │ │ ├── ic_add_40dp.xml
│ │ │ │ ├── ic_add_dark_blue_40dp.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── ic_check_40dp.xml
│ │ │ │ ├── ic_check_dark_40dp.xml
│ │ │ │ ├── ic_arrow_right_32dp.xml
│ │ │ │ ├── ic_arrow_right_40dp.xml
│ │ │ │ ├── ic_arrow_back_40dp.xml
│ │ │ │ ├── ic_arrow_back_blue.xml
│ │ │ │ ├── ic_arrow_forward_blue.xml
│ │ │ │ ├── ic_arrow_right_dark_40dp.xml
│ │ │ │ ├── ic_arrow_up_40dp.xml
│ │ │ │ ├── ic_phrases_arrow_back_blue.xml
│ │ │ │ ├── ic_arrow_back_dark_blue.xml
│ │ │ │ ├── ic_arrow_down_40dp.xml
│ │ │ │ ├── ic_arrow_forward_dark_blue.xml
│ │ │ │ ├── ic_arrow_right_dark_blue_32dp.xml
│ │ │ │ ├── ic_arrow_up_dark_40dp.xml
│ │ │ │ ├── ic_keyboard_arrow_right_dark_40dp.xml
│ │ │ │ ├── ic_phrases_arrow_back_dark_blue.xml
│ │ │ │ ├── ic_phrases_arrow_forward_blue.xml
│ │ │ │ ├── vocable_dialog_button_background.xml
│ │ │ │ ├── ic_arrow_back_dark_blue_40dp.xml
│ │ │ │ ├── ic_arrow_down_dark_40dp.xml
│ │ │ │ ├── ic_phrases_arrow_forward_dark_blue.xml
│ │ │ │ ├── checkmark.xml
│ │ │ │ ├── ic_arrow_right_disabled_40dp.xml
│ │ │ │ ├── ic_check_disabled_40dp.xml
│ │ │ │ ├── ic_delete.xml
│ │ │ │ ├── ic_arrow_right_disabled_32dp.xml
│ │ │ │ ├── ic_delete_56dp.xml
│ │ │ │ ├── ic_delete_dark_blue.xml
│ │ │ │ ├── ic_phrases_arrow_back_disabled.xml
│ │ │ │ ├── ic_phrases_arrow_forward_disabled.xml
│ │ │ │ ├── ic_star_40dp.xml
│ │ │ │ ├── launch_32dp.xml
│ │ │ │ ├── settings_group_highlighted_background.xml
│ │ │ │ ├── arrow_up_40dp.xml
│ │ │ │ ├── ic_arrow_down_disabled_40dp.xml
│ │ │ │ ├── ic_arrow_up_disabled_40dp.xml
│ │ │ │ ├── ic_close.xml
│ │ │ │ ├── ic_pause.xml
│ │ │ │ ├── ic_star_blue_56dp.xml
│ │ │ │ ├── ic_star_dark_40dp.xml
│ │ │ │ ├── add_favorite_action_button_icon.xml
│ │ │ │ ├── arrow_down_40dp.xml
│ │ │ │ ├── ic_close_dark_blue.xml
│ │ │ │ ├── arrow_right_32dp.xml
│ │ │ │ ├── small_arrow_right_40dp.xml
│ │ │ │ ├── button_speaker_background.xml
│ │ │ │ ├── sensitivity_button_background.xml
│ │ │ │ ├── button_highlighted_background.xml
│ │ │ │ ├── phrases_back_button_icon.xml
│ │ │ │ ├── button_default_background_dashed.xml
│ │ │ │ ├── category_button_background.xml
│ │ │ │ ├── ic_launch_32dp.xml
│ │ │ │ ├── radio_button_background.xml
│ │ │ │ ├── button_category_highlighted_background.xml
│ │ │ │ ├── ic_undo.xml
│ │ │ │ ├── phrases_forward_button_icon.xml
│ │ │ │ ├── button_speaker_highlighted_background.xml
│ │ │ │ ├── ic_launch_dark_blue_32dp.xml
│ │ │ │ ├── ic_presets.xml
│ │ │ │ ├── ic_edit_40dp.xml
│ │ │ │ ├── ic_launch_disabled_32dp.xml
│ │ │ │ ├── ic_presets_dark_blue.xml
│ │ │ │ ├── ic_edit_dark_blue_40dp.xml
│ │ │ │ ├── ic_save_40dp.xml
│ │ │ │ ├── ic_save_dark_blue_40dp.xml
│ │ │ │ ├── ic_add_phrase.xml
│ │ │ │ ├── ic_save_dark_40dp.xml
│ │ │ │ ├── button_background_dashed.xml
│ │ │ │ ├── ic_heart_solid_blue.xml
│ │ │ │ ├── ic_heart_solid_dark_blue.xml
│ │ │ │ ├── button_highlighted_background_dashed.xml
│ │ │ │ ├── ic_error.xml
│ │ │ │ ├── ic_speaker.xml
│ │ │ │ ├── ic_star_border_40dp.xml
│ │ │ │ ├── ic_speaker_blue.xml
│ │ │ │ ├── button_default_background.xml
│ │ │ │ ├── ic_backspace.xml
│ │ │ │ ├── button_default_background_remove_category.xml
│ │ │ │ ├── ic_backspace_dark_blue.xml
│ │ │ │ ├── ic_clock.xml
│ │ │ │ ├── ic_keyboard.xml
│ │ │ │ ├── ic_keyboard_dark_blue.xml
│ │ │ │ ├── ic_repeat.xml
│ │ │ │ ├── ic_speak_40dp.xml
│ │ │ │ ├── ic_speak_dark_blue.xml
│ │ │ │ ├── ic_heart_border_blue.xml
│ │ │ │ ├── ic_up_arrow.xml
│ │ │ │ ├── ic_down_arrow.xml
│ │ │ │ ├── ic_visibility_off_40dp.xml
│ │ │ │ ├── ic_settings_dark_blue.xml
│ │ │ │ ├── ic_settings_light_48dp.xml
│ │ │ │ ├── button_shown.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ └── button_hidden.xml
│ │ │ ├── layout-sw600dp
│ │ │ │ ├── category_button.xml
│ │ │ │ ├── categories_fragment.xml
│ │ │ │ ├── keyboard_action_buttons.xml
│ │ │ │ ├── presets_action_buttons.xml
│ │ │ │ └── my_sayings_empty_layout.xml
│ │ │ ├── color
│ │ │ │ ├── category_button_text_colors.xml
│ │ │ │ └── vocable_button_text_color.xml
│ │ │ ├── layout
│ │ │ │ ├── fragment_number_pad.xml
│ │ │ │ ├── fragment_phrases.xml
│ │ │ │ ├── categories_fragment.xml
│ │ │ │ ├── category_button.xml
│ │ │ │ ├── fragment_edit_categories_list.xml
│ │ │ │ ├── fragment_splash.xml
│ │ │ │ ├── phrase_button_add.xml
│ │ │ │ ├── phrase_button.xml
│ │ │ │ ├── keyboard_action_layout.xml
│ │ │ │ ├── keyboard_key_layout.xml
│ │ │ │ ├── guidelines_settings.xml
│ │ │ │ ├── fragment_custom_category_phrase_list.xml
│ │ │ │ ├── vocable_switch_layout.xml
│ │ │ │ ├── error_layout.xml
│ │ │ │ ├── presets_action_buttons.xml
│ │ │ │ ├── my_sayings_empty_layout.xml
│ │ │ │ ├── keyboard_action_buttons.xml
│ │ │ │ ├── edit_custom_category_phrase_item.xml
│ │ │ │ ├── settings_options_layout.xml
│ │ │ │ └── edit_custom_category_phrase_action_buttons.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable-sw600dp
│ │ │ │ └── ic_heart_solid_blue.xml
│ │ │ ├── layout-land
│ │ │ │ ├── keyboard_action_buttons.xml
│ │ │ │ ├── presets_action_buttons.xml
│ │ │ │ ├── my_sayings_empty_layout.xml
│ │ │ │ ├── edit_custom_category_phrase_item.xml
│ │ │ │ └── edit_phrases_action_button.xml
│ │ │ ├── layout-sw600dp-land
│ │ │ │ └── presets_action_buttons.xml
│ │ │ ├── values-sw600dp-land
│ │ │ │ └── integers.xml
│ │ │ ├── values-land
│ │ │ │ ├── arrays.xml
│ │ │ │ └── integers.xml
│ │ │ ├── values-sw400dp-land
│ │ │ │ └── integers.xml
│ │ │ └── values-sw400dp
│ │ │ │ └── integers.xml
│ │ ├── assets
│ │ │ └── fonts
│ │ │ │ └── MaterialIcons-Regular.ttf
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── willowtree
│ │ │ │ └── vocable
│ │ │ │ ├── utils
│ │ │ │ ├── DateProvider.kt
│ │ │ │ ├── UUIDProvider.kt
│ │ │ │ ├── locale
│ │ │ │ │ ├── LocaleProvider.kt
│ │ │ │ │ ├── LocaleWithText.kt
│ │ │ │ │ ├── JavaLocaleProvider.kt
│ │ │ │ │ └── LocalizedResourceUtility.kt
│ │ │ │ ├── JavaDateProvider.kt
│ │ │ │ ├── RandomUUIDProvider.kt
│ │ │ │ ├── SpokenText.kt
│ │ │ │ ├── permissions
│ │ │ │ │ ├── PermissionsChecker.kt
│ │ │ │ │ ├── PermissionsRationaleDialogShower.kt
│ │ │ │ │ ├── PermissionRegisterForLauncher.kt
│ │ │ │ │ ├── ActivityPermissionsChecker.kt
│ │ │ │ │ └── ActivityPermissionRegisterForLaunch.kt
│ │ │ │ ├── ILocalizedResourceUtility.kt
│ │ │ │ ├── VocableEnvironment.kt
│ │ │ │ ├── IFaceTrackingPermissions.kt
│ │ │ │ ├── IdlingResourceContainer.kt
│ │ │ │ └── IVocableSharedPreferences.kt
│ │ │ │ ├── customviews
│ │ │ │ ├── PointerListener.kt
│ │ │ │ ├── NoSayTextButton.kt
│ │ │ │ ├── VocablePhraseButton.kt
│ │ │ │ ├── PointerView.kt
│ │ │ │ ├── ActionButton.kt
│ │ │ │ └── VocableSwitch.kt
│ │ │ │ ├── settings
│ │ │ │ ├── editcategories
│ │ │ │ │ └── EditCategoriesPage.kt
│ │ │ │ ├── customcategories
│ │ │ │ │ └── CustomCategoryPhraseViewModel.kt
│ │ │ │ ├── selectionmode
│ │ │ │ │ └── SelectionModeViewModel.kt
│ │ │ │ ├── EditCategoryPhrasesViewModel.kt
│ │ │ │ └── EditPhrasesViewModel.kt
│ │ │ │ ├── room
│ │ │ │ ├── CategorySortOrder.kt
│ │ │ │ ├── PresetCategoryHidden.kt
│ │ │ │ ├── StoredCategoryHidden.kt
│ │ │ │ ├── PhraseSpokenDate.kt
│ │ │ │ ├── PresetCategoryDeleted.kt
│ │ │ │ ├── Version7Migration.kt
│ │ │ │ ├── CategoryLocalizedName.kt
│ │ │ │ ├── PhraseLocalizedUtterance.kt
│ │ │ │ ├── PresetCategoryDto.kt
│ │ │ │ ├── StoredCategoriesRepository.kt
│ │ │ │ ├── CategoryDto.kt
│ │ │ │ ├── PresetPhraseDto.kt
│ │ │ │ ├── PresetPhrasesRepository.kt
│ │ │ │ ├── StoredPhrasesRepository.kt
│ │ │ │ ├── PhraseDto.kt
│ │ │ │ ├── PresetCategoryDao.kt
│ │ │ │ ├── PhraseDao.kt
│ │ │ │ └── RoomStoredCategoriesRepository.kt
│ │ │ │ ├── presets
│ │ │ │ ├── PhraseGridItem.kt
│ │ │ │ ├── PresetCategoriesRepository.kt
│ │ │ │ ├── PresetCategories.kt
│ │ │ │ └── ILegacyCategoriesAndPhrasesRepository.kt
│ │ │ │ ├── IPhrasesUseCase.kt
│ │ │ │ ├── splash
│ │ │ │ ├── SplashActivity.kt
│ │ │ │ └── SplashViewModel.kt
│ │ │ │ ├── ICategoriesUseCase.kt
│ │ │ │ └── VocableApp.kt
│ │ └── AndroidManifest.xml
│ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── willowtree
│ │ │ └── vocable
│ │ │ ├── utility
│ │ │ ├── TestApplication.kt
│ │ │ ├── FakeDateProvider.kt
│ │ │ ├── VocableTestEnvironment.kt
│ │ │ ├── vocableTestModule.kt
│ │ │ ├── VocableTestRunner.kt
│ │ │ ├── InMemoryDatabase.kt
│ │ │ ├── IdlingResourceContainerTestingImpl.kt
│ │ │ ├── ViewInteractionExt.kt
│ │ │ └── VocableKoinTestRule.kt
│ │ │ ├── FakeUUIDProvider.kt
│ │ │ ├── MainDispatcherRule.kt
│ │ │ ├── tests
│ │ │ ├── BaseTest.kt
│ │ │ └── MainScreenTest.kt
│ │ │ ├── presets
│ │ │ └── RoomPresetCategoriesRepositoryTest.kt
│ │ │ └── screens
│ │ │ └── KeyboardScreen.kt
│ └── test
│ │ └── java
│ │ └── com
│ │ └── willowtree
│ │ └── vocable
│ │ ├── utils
│ │ ├── ConstantUUIDProvider.kt
│ │ ├── FakeDateProvider.kt
│ │ ├── permissions
│ │ │ ├── FakePermissionsChecker.kt
│ │ │ ├── FakePermissionRegisterForLaunch.kt
│ │ │ └── FakePermissionsRationaleDialogShower.kt
│ │ ├── FakeFaceTrackingPermissions.kt
│ │ └── FakeLocalizedResourceUtility.kt
│ │ ├── MainDispatcherRule.kt
│ │ └── LiveDataExt.kt
└── proguard-rules.pro
├── basetest
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── willowtree
│ │ └── vocable
│ │ └── basetest
│ │ └── utils
│ │ ├── FakeLocaleProvider.kt
│ │ └── presets
│ │ └── CategoryExt.kt
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── CODEOWNERS
├── marketing_assets
└── vocable_vimeo_still.gif
├── pull_request_template.md
├── .github
├── ISSUE_TEMPLATE
│ ├── development-task.md
│ ├── design-task.md
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── build.yml
│ └── metrics.yml
├── crowdin.yml
├── settings.gradle.kts
├── ROADMAP.md
├── Documentation
├── ReleaseProcess.md
└── Firebase.md
├── .gitignore
├── LICENSE.md
├── gradle.properties
└── CONTRIBUTING.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/basetest/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/basetest/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/.DS_Store
--------------------------------------------------------------------------------
/app/src/main/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/.DS_Store
--------------------------------------------------------------------------------
/app/src/main/res/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/.DS_Store
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @nicholas-cook @jrtb @stechkev @nerdsosky @jollygreenegiant @willdenne @cxw320 @paulklauser @mattttvaughn @SashankPatel
2 |
--------------------------------------------------------------------------------
/marketing_assets/vocable_vimeo_still.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/marketing_assets/vocable_vimeo_still.gif
--------------------------------------------------------------------------------
/app/src/main/res/values/booleans.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/booleans.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/assets/fonts/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/vocable-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/DateProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | interface DateProvider {
4 | fun currentTimeMillis(): Long
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/UUIDProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | interface UUIDProvider {
4 | fun randomUUIDString(): String
5 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/TestApplication.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import android.app.Application
4 |
5 | class TestApplication: Application()
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Description:
2 | say what this PR does
3 |
4 | Screenshots:
5 | insert screenshots of your work
6 |
7 | - [ ] Acceptance Criteria satisfied
8 | - [ ] Regression Testing
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/locale/LocaleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.locale
2 |
3 | interface LocaleProvider {
4 | fun getDefaultLocaleString(): LocaleString
5 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-de/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 5
4 | 7
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 5
4 | 7
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/customviews/PointerListener.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.customviews
2 |
3 | interface PointerListener {
4 |
5 | fun onPointerEnter()
6 |
7 | fun onPointerExit()
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-de-land/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 11
4 | 3
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de-sw600dp/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 11
4 | 3
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it-land/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 12
4 | 3
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de-sw600dp-land/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 11
4 | 3
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/JavaDateProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | class JavaDateProvider : DateProvider {
4 | override fun currentTimeMillis(): Long = System.currentTimeMillis()
5 | }
--------------------------------------------------------------------------------
/basetest/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("vocable.library")
3 | }
4 |
5 | android {
6 | namespace = "com.willowtree.vocable.basetest"
7 | }
8 |
9 | dependencies {
10 | implementation(project(":app"))
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/keyboard_speak_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/ConstantUUIDProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | class ConstantUUIDProvider : UUIDProvider {
4 |
5 | var _uuid = "1"
6 |
7 | override fun randomUUIDString(): String = _uuid
8 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/FakeDateProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | class FakeDateProvider : DateProvider {
4 |
5 | var _currentTimeMillis = 0L
6 |
7 | override fun currentTimeMillis(): Long = _currentTimeMillis
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/settings/editcategories/EditCategoriesPage.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.settings.editcategories
2 |
3 | import com.willowtree.vocable.presets.Category
4 |
5 | data class EditCategoriesPage(
6 | val categories: List
7 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/locale/LocaleWithText.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.locale
2 |
3 | import java.util.Locale
4 |
5 |
6 | typealias LocaleWithText = Pair
7 | fun LocaleWithText.locale() = first
8 | fun LocaleWithText.text() = second
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 25 09:17:15 EDT 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/RandomUUIDProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import java.util.UUID
4 |
5 | class RandomUUIDProvider : UUIDProvider {
6 | override fun randomUUIDString(): String {
7 | return UUID.randomUUID().toString()
8 | }
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp/category_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/SpokenText.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import androidx.lifecycle.MutableLiveData
4 |
5 | /**
6 | * A LiveData object that represents the text that was most recently spoken aloud by TTS
7 | */
8 | object SpokenText : MutableLiveData()
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/locale/JavaLocaleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.locale
2 |
3 | import java.util.Locale
4 |
5 | class JavaLocaleProvider : LocaleProvider {
6 | override fun getDefaultLocaleString(): String =
7 | Locale.getDefault().toString()
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/permissions/PermissionsChecker.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | interface PermissionsChecker {
4 | fun hasPermissions(permission: String): Boolean
5 | fun shouldShowRequestPermissionRationale(permission: String): Boolean
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_save.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/saved_phrase_success_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/CategorySortOrder.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 |
5 | data class CategorySortOrder(
6 | @ColumnInfo(name = "category_id") val categoryId: String,
7 | @ColumnInfo(name = "sort_order") var sortOrder: Int
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PresetCategoryHidden.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 |
5 | data class PresetCategoryHidden(
6 | @ColumnInfo(name = "category_id") val categoryId: String,
7 | @ColumnInfo(name = "hidden") val hidden: Boolean
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/StoredCategoryHidden.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 |
5 | data class StoredCategoryHidden(
6 | @ColumnInfo(name = "category_id") val categoryId: String,
7 | @ColumnInfo(name = "hidden") val hidden: Boolean
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/res/color/category_button_text_colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/add_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/basetest/src/main/java/com/willowtree/vocable/basetest/utils/FakeLocaleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.basetest.utils
2 |
3 | import com.willowtree.vocable.utils.locale.LocaleProvider
4 |
5 | class FakeLocaleProvider : LocaleProvider {
6 | override fun getDefaultLocaleString(): String = "en_US"
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PhraseSpokenDate.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 |
5 | data class PhraseSpokenDate(
6 | @ColumnInfo(name = "phrase_id") val phraseId: String,
7 | @ColumnInfo(name = "last_spoken_date") val lastSpokenDate: Long
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PresetCategoryDeleted.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 |
5 | data class PresetCategoryDeleted(
6 | @ColumnInfo(name = "category_id") val categoryId: String,
7 | @ColumnInfo(name = "deleted") val deleted: Boolean
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/edit_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/error_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/save_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_number_pad.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_phrases.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_speaker.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/close_action_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/presets_action_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/arrow_back_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_selected_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/category_group_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/decrease_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/keyboard_delete_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/pointer_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_background_remove_category.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/keyboard_action_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/keyboard_space_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/radio_button_default_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/splash_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_background_disabled.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/category_back_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_resume.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/keyboard_backspace_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/categories_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/FakeDateProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import com.willowtree.vocable.utils.DateProvider
4 |
5 | class FakeDateProvider : DateProvider {
6 |
7 | var time = 0L
8 |
9 | override fun currentTimeMillis(): Long {
10 | return time
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/Version7Migration.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.DeleteColumn
4 | import androidx.room.migration.AutoMigrationSpec
5 |
6 | @DeleteColumn(
7 | tableName = "Category",
8 | columnName = "resource_id"
9 | )
10 | class Version7Migration : AutoMigrationSpec
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings_action_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings_group_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/category_forward_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_decrease_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/FakeUUIDProvider.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import com.willowtree.vocable.utils.UUIDProvider
4 |
5 | class FakeUUIDProvider : UUIDProvider {
6 |
7 | private var _uuid = 1
8 |
9 | override fun randomUUIDString(): String {
10 | return _uuid++.toString()
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_space_bar_56dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_space_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_decrease_dark_blue_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/VocableTestEnvironment.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import com.willowtree.vocable.utils.VocableEnvironment
4 | import com.willowtree.vocable.utils.VocableEnvironmentType
5 |
6 | class VocableTestEnvironment: VocableEnvironment {
7 | override val environmentType = VocableEnvironmentType.TESTING
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_dark_blue_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/category_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/ILocalizedResourceUtility.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import com.willowtree.vocable.presets.Category
4 | import com.willowtree.vocable.presets.Phrase
5 |
6 | interface ILocalizedResourceUtility {
7 | fun getTextFromCategory(category: Category?): String
8 | fun getTextFromPhrase(phrase: Phrase?): String
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/vocableTestModule.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import com.willowtree.vocable.utils.VocableEnvironment
4 | import org.koin.dsl.module
5 |
6 |
7 | val vocableTestModule = module {
8 | single { getInMemoryVocableDatabase() }
9 | single { VocableTestEnvironment() }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right_32dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_forward_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_up_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phrases_arrow_back_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_edit_categories_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/CategoryLocalizedName.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 | import com.willowtree.vocable.utils.locale.LocalesWithText
5 |
6 | data class CategoryLocalizedName(
7 | @ColumnInfo(name = "category_id") val categoryId: String,
8 | @ColumnInfo(name = "localized_name") var localizedName: LocalesWithText
9 | )
--------------------------------------------------------------------------------
/app/src/main/res/color/vocable_button_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_down_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_forward_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right_dark_blue_32dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_up_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keyboard_arrow_right_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phrases_arrow_back_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phrases_arrow_forward_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vocable_dialog_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_dark_blue_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_down_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phrases_arrow_forward_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/checkmark.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right_disabled_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_disabled_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right_disabled_32dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_56dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp/categories_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/development-task.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Development Task
3 | about: Used for creating new issues for development
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Acceptance Criteria**:
11 |
12 | any scenarios or conditions that need to be satisfied
13 | If you're familiar with Gherkin, please use that format.
14 |
15 | **Design**:
16 |
17 | Screenshots or Link to Figma
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phrases_arrow_back_disabled.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phrases_arrow_forward_disabled.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/launch_32dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings_group_highlighted_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PhraseLocalizedUtterance.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 | import com.willowtree.vocable.utils.locale.LocalesWithText
5 |
6 | data class PhraseLocalizedUtterance(
7 | @ColumnInfo(name = "phrase_id") val phraseId: String,
8 | @ColumnInfo(name = "localized_utterance") var localizedUtterance: LocalesWithText
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/arrow_up_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_down_disabled_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_up_disabled_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_blue_56dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/add_favorite_action_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/arrow_down_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/arrow_right_32dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/small_arrow_right_40dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_speaker_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sensitivity_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/VocableEnvironment.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | enum class VocableEnvironmentType {
4 | PRODUCTION,
5 | TESTING
6 | }
7 |
8 |
9 | interface VocableEnvironment {
10 | val environmentType: VocableEnvironmentType
11 | }
12 |
13 | class VocableEnvironmentImpl : VocableEnvironment {
14 | override val environmentType: VocableEnvironmentType = VocableEnvironmentType.PRODUCTION
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_highlighted_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/phrases_back_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_default_background_dashed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/category_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launch_32dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/radio_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_category_highlighted_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_undo.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/phrases_forward_button_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/presets/PhraseGridItem.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.presets
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | sealed class PhraseGridItem : Parcelable {
8 |
9 | @Parcelize
10 | data class Phrase(
11 | val phraseId: String,
12 | val text: String
13 | ) : PhraseGridItem()
14 |
15 | @Parcelize
16 | object AddPhrase : PhraseGridItem()
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_speaker_highlighted_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launch_dark_blue_32dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/design-task.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Design Task
3 | about: Used to create new design task for Vocable
4 | title: ''
5 | labels: Design Updates
6 | assignees: ''
7 |
8 | ---
9 |
10 | #### Design - Area of Focus
11 | This is a template. Description of what you are working on.
12 |
13 | - [x] Item 1 to work on
14 | - [ ] Item 2 to work on
15 | - [ ] Item 3 to work on
16 |
17 | *or*
18 | * Item 1 to work on
19 | * Item 2 to work on
20 | * Item 3 to work on
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_presets.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launch_disabled_32dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_presets_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | # path to strings that should be translated
3 | - source: app/src/main/res/values/strings.xml
4 | # path to translated files
5 | translation: /app/src/main/res/values-%android_code%/%original_file_name%
6 | # whether attributes (xml tags) should be translated. 0 - no, 1 - yes
7 | translate_attributes: 0
8 | # whether long translations should be split into multiple string resources. 0 - no, 1 - yes
9 | content_segmentation: 0
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit_dark_blue_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_save_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_save_dark_blue_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_phrase.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_save_dark_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_background_dashed.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_heart_solid_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_heart_solid_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-sw600dp/ic_heart_solid_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_highlighted_background_dashed.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_error.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/permissions/PermissionsRationaleDialogShower.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | interface PermissionsRationaleDialogShower {
4 |
5 | fun showPermissionRationaleDialog(
6 | onPositiveClick: () -> Unit,
7 | onNegativeClick: () -> Unit,
8 | onDismiss: () -> Unit,
9 | )
10 |
11 | fun showSettingsPermissionRationaleDialog(
12 | onPositiveClick: () -> Unit,
13 | onNegativeClick: () -> Unit,
14 | )
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_speaker.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_border_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_speaker_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_default_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PresetCategoryDto.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(tableName = "PresetCategory")
8 | data class PresetCategoryDto(
9 | @PrimaryKey @ColumnInfo(name = "category_id") val categoryId: String,
10 | @ColumnInfo(name = "hidden") val hidden: Boolean,
11 | @ColumnInfo(name = "sort_order") val sortOrder: Int,
12 | @ColumnInfo(name = "deleted") val deleted: Boolean
13 | )
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/VocableTestRunner.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.test.runner.AndroidJUnitRunner
6 |
7 | class VocableTestRunner: AndroidJUnitRunner() {
8 | override fun newApplication(
9 | cl: ClassLoader?,
10 | className: String?,
11 | context: Context?
12 | ): Application {
13 | return super.newApplication(cl, TestApplication::class.java.name, context)
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/InMemoryDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import androidx.room.Room
4 | import androidx.test.core.app.ApplicationProvider
5 | import com.willowtree.vocable.room.VocableDatabase
6 | import com.willowtree.vocable.room.addVocableMigrations
7 |
8 | fun getInMemoryVocableDatabase() = Room
9 | .inMemoryDatabaseBuilder(
10 | ApplicationProvider.getApplicationContext(),
11 | VocableDatabase::class.java
12 | )
13 | .addVocableMigrations()
14 | .build()
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_backspace.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/phrase_button_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/permissions/PermissionRegisterForLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | import androidx.activity.result.contract.ActivityResultContract
4 |
5 | interface PermissionRequester {
6 | fun registerForActivityResult(
7 | contract: ActivityResultContract,
8 | activityResultCallback: (Boolean) -> Unit,
9 | ) : PermissionRequestLauncher
10 | }
11 |
12 | interface PermissionRequestLauncher {
13 | fun launch(input: String)
14 | fun unregister()
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_default_background_remove_category.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_backspace_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/phrase_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/permissions/FakePermissionsChecker.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | class FakePermissionsChecker(
4 | private val hasPermissions: Boolean = false,
5 | private val shouldShowRequestPermissionRationale: Boolean = true,
6 | ): PermissionsChecker {
7 | override fun hasPermissions(permission: String): Boolean {
8 | return hasPermissions
9 | }
10 | override fun shouldShowRequestPermissionRationale(permission: String): Boolean {
11 | return shouldShowRequestPermissionRationale
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_clock.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keyboard.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/customviews/NoSayTextButton.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.customviews
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.util.AttributeSet
6 |
7 | /**
8 | * A subclass of VocableButton that will not say what is on the button when it is clicked.
9 | */
10 | class NoSayTextButton @JvmOverloads constructor(
11 | context: Context,
12 | attrs: AttributeSet? = null,
13 | defStyle: Int = 0
14 | ) : ActionButton(context, attrs, defStyle) {
15 |
16 | override fun sayText(text: CharSequence?) {
17 | //no-op
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/customviews/VocablePhraseButton.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.customviews
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 |
6 | /**
7 | * A class that allows you to pass a custom action to a VocableButton
8 | */
9 | class VocablePhraseButton @JvmOverloads constructor(
10 | context: Context,
11 | attrs: AttributeSet? = null,
12 | defStyle: Int = 0
13 | ) : VocableButton(context, attrs, defStyle) {
14 |
15 | var action: (() -> Unit)? = null
16 |
17 | override fun performAction() {
18 | action?.invoke()
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keyboard_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/presets/PresetCategoriesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.presets
2 |
3 | import com.willowtree.vocable.room.CategorySortOrder
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface PresetCategoriesRepository {
7 | fun getPresetCategories(): Flow>
8 | suspend fun updateCategorySortOrders(categorySortOrders: List)
9 | suspend fun getCategoryById(categoryId: String): Category.PresetCategory?
10 | suspend fun updateCategoryHidden(categoryId: String, hidden: Boolean)
11 | suspend fun deleteCategory(categoryId: String)
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/IFaceTrackingPermissions.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import kotlinx.coroutines.flow.MutableStateFlow
4 |
5 |
6 | interface IFaceTrackingPermissions {
7 |
8 | sealed interface PermissionState {
9 | object Enabled : PermissionState
10 | object Disabled : PermissionState
11 | }
12 |
13 | val permissionState: MutableStateFlow
14 |
15 | fun requestFaceTracking()
16 |
17 | fun disableFaceTracking()
18 | }
19 |
20 | fun IFaceTrackingPermissions.PermissionState.isEnabled() = this == IFaceTrackingPermissions.PermissionState.Enabled
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_repeat.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_speak_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | // Needed because of https://issuetracker.google.com/issues/315023802
2 | gradle.startParameter.excludedTaskNames.addAll(listOf(":build-logic:testClasses"))
3 |
4 | pluginManagement {
5 | includeBuild("build-logic")
6 | repositories {
7 | google()
8 | mavenCentral()
9 | gradlePluginPortal()
10 | }
11 | }
12 | dependencyResolutionManagement {
13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
14 | repositories {
15 | google()
16 | mavenCentral()
17 | }
18 | }
19 | rootProject.name = "vocable-android"
20 |
21 | include(":app")
22 | include(":basetest")
23 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Vocable Roadmap
2 |
3 | - [ ] Handset Support
4 | - [ ] Edit and delete customized presets
5 | - [ ] Custom Categories: create, edit, and delete
6 | - [ ] Custom category/phrase sorting and UI placement
7 | - [ ] History: recently-spoken phrases
8 | - [ ] Head tracking calibration
9 | - [ ] Audio feedback for navigation
10 | - [ ] Potential Voice integration
11 | - [ ] Alternative modalities / input actions (e.g. touch, joystick method, scanning)
12 | - [ ] Light / Dark Mode
13 | - [ ] Localization of app strings
14 | - [ ] Localization of keyboard(s)
15 | - [ ] Photo upload / pictoral buttons
16 | - [ ] Translate selected phrase ↔️ spoken phrase
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_speak_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/basetest/src/main/java/com/willowtree/vocable/basetest/utils/presets/CategoryExt.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.basetest.utils.presets
2 |
3 | import com.willowtree.vocable.presets.Category
4 | import com.willowtree.vocable.utils.locale.LocalesWithText
5 |
6 | fun createStoredCategory(
7 | categoryId: String,
8 | localizedName: LocalesWithText = LocalesWithText(mapOf("en_US" to "category")),
9 | hidden: Boolean = false,
10 | sortOrder: Int = 0
11 | ): Category.StoredCategory = Category.StoredCategory(
12 | categoryId = categoryId,
13 | localizedName = localizedName,
14 | hidden = hidden,
15 | sortOrder = sortOrder,
16 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/IdlingResourceContainer.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | interface IdlingResourceContainer {
4 | fun decrement()
5 | fun increment()
6 |
7 | suspend fun run(action: suspend () -> T): T {
8 | increment()
9 | try {
10 | return action()
11 | } finally {
12 | decrement()
13 | }
14 | }
15 | }
16 |
17 | class IdlingResourceContainerImpl : IdlingResourceContainer {
18 | override fun decrement() {
19 | // Do nothing in prod
20 | }
21 |
22 | override fun increment() {
23 | // Do nothing in prod
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/StoredCategoriesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import com.willowtree.vocable.presets.Category
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface StoredCategoriesRepository {
7 | fun getAllCategories(): Flow>
8 | suspend fun upsertCategory(category: Category.StoredCategory)
9 | suspend fun updateCategorySortOrders(categorySortOrders: List)
10 | suspend fun getCategoryById(categoryId: String): CategoryDto?
11 | suspend fun updateCategoryHidden(categoryId: String, hidden: Boolean)
12 | suspend fun deleteCategory(categoryId: String)
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/settings/customcategories/CustomCategoryPhraseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.settings.customcategories
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.willowtree.vocable.IPhrasesUseCase
6 | import com.willowtree.vocable.presets.Phrase
7 | import kotlinx.coroutines.launch
8 |
9 | class CustomCategoryPhraseViewModel(
10 | private val phrasesUseCase: IPhrasesUseCase
11 | ) : ViewModel() {
12 | fun deletePhraseFromCategory(phrase: Phrase) {
13 | viewModelScope.launch {
14 | phrasesUseCase.deletePhrase(phrase.phraseId)
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_heart_border_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyboard_action_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/locale/LocalizedResourceUtility.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.locale
2 |
3 | import android.content.Context
4 | import com.willowtree.vocable.presets.Category
5 | import com.willowtree.vocable.presets.Phrase
6 | import com.willowtree.vocable.utils.ILocalizedResourceUtility
7 |
8 | class LocalizedResourceUtility(
9 | private val context: Context,
10 | ) : ILocalizedResourceUtility {
11 |
12 | override fun getTextFromCategory(category: Category?): String {
13 | return category?.text(context) ?: ""
14 | }
15 |
16 | override fun getTextFromPhrase(phrase: Phrase?): String {
17 | return phrase?.text(context) ?: ""
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/FakeFaceTrackingPermissions.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import kotlinx.coroutines.flow.MutableStateFlow
4 |
5 |
6 | class FakeFaceTrackingPermissions(enabled: Boolean) : IFaceTrackingPermissions {
7 |
8 | override val permissionState: MutableStateFlow =
9 | MutableStateFlow(if (enabled) IFaceTrackingPermissions.PermissionState.Enabled else IFaceTrackingPermissions.PermissionState.Disabled)
10 |
11 | override fun requestFaceTracking() {}
12 |
13 | override fun disableFaceTracking() {
14 | permissionState.tryEmit(IFaceTrackingPermissions.PermissionState.Disabled)
15 | }
16 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 |
21 | **Actual Behavior**
22 | Clear and concise description of what happened
23 |
24 |
25 | **Screenshots**
26 | If applicable, add screenshots to help explain your problem.
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. Pixel 4]
30 | - OS: [e.g. Android 10]
31 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/IdlingResourceContainerTestingImpl.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import androidx.test.espresso.IdlingResource
4 | import androidx.test.espresso.idling.CountingIdlingResource
5 | import com.willowtree.vocable.utils.IdlingResourceContainer
6 |
7 | class IdlingResourceContainerTestingImpl(name: String): IdlingResourceContainer {
8 |
9 | private val countingIdlingResource = CountingIdlingResource(name)
10 | val idlingResource: IdlingResource = countingIdlingResource
11 |
12 | override fun increment() {
13 | countingIdlingResource.increment()
14 | }
15 |
16 | override fun decrement() {
17 | countingIdlingResource.decrement()
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/IPhrasesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import com.willowtree.vocable.presets.Phrase
4 | import com.willowtree.vocable.utils.locale.LocalesWithText
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface IPhrasesUseCase {
8 |
9 | suspend fun getPhrasesForCategory(categoryId: String): List
10 |
11 | fun getPhrasesForCategoryFlow(categoryId: String): Flow>
12 |
13 | suspend fun updatePhraseLastSpokenTime(phraseId: String)
14 |
15 | suspend fun deletePhrase(phraseId: String)
16 |
17 | suspend fun updatePhrase(phraseId: String, updatedPhrase: String)
18 |
19 | suspend fun addPhrase(localizedUtterance: LocalesWithText, parentCategoryId: String)
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/CategoryDto.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import android.os.Parcelable
4 | import androidx.room.ColumnInfo
5 | import androidx.room.Entity
6 | import androidx.room.PrimaryKey
7 | import com.willowtree.vocable.utils.locale.LocalesWithText
8 | import kotlinx.parcelize.Parcelize
9 |
10 | @Entity(tableName = "Category")
11 | @Parcelize
12 | data class CategoryDto(
13 | @PrimaryKey @ColumnInfo(name = "category_id") val categoryId: String,
14 | @ColumnInfo(name = "creation_date") val creationDate: Long,
15 | @ColumnInfo(name = "localized_name") var localizedName: LocalesWithText,
16 | var hidden: Boolean,
17 | @ColumnInfo(name = "sort_order") var sortOrder: Int
18 | ) : Parcelable
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/ViewInteractionExt.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import androidx.test.espresso.ViewInteraction
4 | import androidx.test.espresso.action.ViewActions
5 | import androidx.test.espresso.assertion.ViewAssertions
6 | import androidx.test.espresso.matcher.ViewMatchers
7 | import org.hamcrest.CoreMatchers
8 |
9 | fun ViewInteraction.tap() {
10 | perform(ViewActions.click())
11 | }
12 |
13 | fun ViewInteraction.assertTextMatches(text: String) {
14 | check(ViewAssertions.matches(ViewMatchers.withText(CoreMatchers.containsString(text))))
15 | }
16 |
17 | fun ViewInteraction.assertElementExists() {
18 | check(ViewAssertions.matches(CoreMatchers.not(ViewAssertions.doesNotExist())))
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_up_arrow.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_down_arrow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyboard_key_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/MainDispatcherRule.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.test.TestDispatcher
5 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.setMain
8 | import org.junit.rules.TestWatcher
9 | import org.junit.runner.Description
10 |
11 | class MainDispatcherRule(
12 | val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
13 | ) : TestWatcher() {
14 | override fun starting(description: Description) {
15 | Dispatchers.setMain(testDispatcher)
16 | }
17 |
18 | override fun finished(description: Description) {
19 | Dispatchers.resetMain()
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/MainDispatcherRule.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.test.TestDispatcher
5 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.setMain
8 | import org.junit.rules.TestWatcher
9 | import org.junit.runner.Description
10 |
11 | class MainDispatcherRule(
12 | val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
13 | ) : TestWatcher() {
14 | override fun starting(description: Description) {
15 | Dispatchers.setMain(testDispatcher)
16 | }
17 |
18 | override fun finished(description: Description) {
19 | Dispatchers.resetMain()
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/splash/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.splash
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import com.willowtree.vocable.MainActivity
6 | import org.koin.androidx.scope.ScopeActivity
7 | import org.koin.androidx.viewmodel.ext.android.viewModel
8 |
9 | class SplashActivity : ScopeActivity() {
10 |
11 | private val viewModel: SplashViewModel by viewModel()
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 |
16 | viewModel.exitSplash.observe(this) {
17 | if (it) {
18 | startActivity(Intent(this, MainActivity::class.java))
19 | finish()
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3831A0
4 | #803831A0
5 | #201C5B
6 | #59201C5B
7 | #504B93
8 | #D9504B93
9 | #41201C5B
10 | #D1EEE9
11 | #32D1EEE9
12 | #4DD9F9
13 | #FEA430
14 | #00FA9A
15 | #AD006C
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PresetPhraseDto.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import android.os.Parcelable
4 | import androidx.room.ColumnInfo
5 | import androidx.room.Entity
6 | import androidx.room.PrimaryKey
7 | import kotlinx.parcelize.Parcelize
8 |
9 | @Entity(tableName = "PresetPhrase")
10 | @Parcelize
11 | data class PresetPhraseDto(
12 | @PrimaryKey @ColumnInfo(name = "phrase_id") val phraseId: String,
13 | @ColumnInfo(name = "parent_category_id") val parentCategoryId: String,
14 | @ColumnInfo(name = "creation_date") val creationDate: Long,
15 | @ColumnInfo(name = "last_spoken_date") val lastSpokenDate: Long?,
16 | @ColumnInfo(name = "sort_order") val sortOrder: Int,
17 | @ColumnInfo(name = "deleted") val deleted: Boolean = false,
18 | ) : Parcelable
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import com.willowtree.vocable.presets.PresetPhrase
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface PresetPhrasesRepository {
7 | suspend fun populateDatabase()
8 | suspend fun getAllPresetPhrases(): List
9 | suspend fun updatePhraseLastSpokenTime(phraseId: String)
10 | suspend fun getRecentPhrases() : List
11 | fun getRecentPhrasesFlow(): Flow>
12 | suspend fun getPhrasesForCategory(categoryId: String): List
13 | fun getPhrasesForCategoryFlow(categoryId: String): Flow>
14 | suspend fun getPhrase(phraseId: String): PresetPhrase?
15 | suspend fun deletePhrase(phraseId: String)
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/customviews/PointerView.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.customviews
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.constraintlayout.widget.ConstraintLayout
7 |
8 | class PointerView(context: Context, attrs: AttributeSet) : View(context, attrs) {
9 |
10 | private var xAdjusted: Float = 0f
11 | private var yAdjusted: Float = 0f
12 |
13 | fun updatePointerPosition(x: Float, y: Float) {
14 | this.xAdjusted = x
15 | this.yAdjusted = y
16 | val params = layoutParams as ConstraintLayout.LayoutParams
17 | layoutParams = params.apply {
18 | marginStart = x.toInt()
19 | topMargin = y.toInt()
20 | }
21 | invalidate()
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/StoredPhrasesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import com.willowtree.vocable.presets.Phrase
4 | import com.willowtree.vocable.utils.locale.LocalesWithText
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface StoredPhrasesRepository {
8 | suspend fun addPhrase(phrase: PhraseDto)
9 | suspend fun updatePhraseLastSpokenTime(phraseId: String)
10 | fun getRecentPhrasesFlow(): Flow>
11 | fun getPhrasesForCategoryFlow(categoryId: String): Flow>
12 | suspend fun getPhrase(phraseId: String): Phrase?
13 | suspend fun updatePhrase(phrase: PhraseDto)
14 | suspend fun updatePhraseLocalizedUtterance(
15 | phraseId: String,
16 | localizedUtterance: LocalesWithText,
17 | )
18 | suspend fun deletePhrase(phraseId: String)
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PhraseDto.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import android.os.Parcelable
4 | import androidx.room.ColumnInfo
5 | import androidx.room.Entity
6 | import androidx.room.PrimaryKey
7 | import com.willowtree.vocable.utils.locale.LocalesWithText
8 | import kotlinx.parcelize.Parcelize
9 |
10 | @Entity(tableName = "Phrase")
11 | @Parcelize
12 | data class PhraseDto(
13 | @PrimaryKey @ColumnInfo(name = "phrase_id") val phraseId: String,
14 | @ColumnInfo(name = "parent_category_id") val parentCategoryId: String?,
15 | @ColumnInfo(name = "creation_date") val creationDate: Long,
16 | @ColumnInfo(name = "last_spoken_date") val lastSpokenDate: Long?,
17 | @ColumnInfo(name = "localized_utterance") val localizedUtterance: LocalesWithText?,
18 | @ColumnInfo(name = "sort_order") val sortOrder: Int,
19 | ) : Parcelable
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/tests/BaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.tests
2 |
3 | import androidx.test.ext.junit.rules.ActivityScenarioRule
4 | import androidx.test.rule.GrantPermissionRule
5 | import com.willowtree.vocable.splash.SplashActivity
6 | import com.willowtree.vocable.utility.IdlingResourceTestRule
7 | import com.willowtree.vocable.utility.VocableKoinTestRule
8 | import org.junit.Rule
9 |
10 | open class BaseTest {
11 |
12 | @get:Rule(order = 0)
13 | val vocableKoinTestRule = VocableKoinTestRule()
14 |
15 | @get:Rule(order = 1)
16 | val idlingResourceTestRule = IdlingResourceTestRule()
17 |
18 | @get:Rule(order = 2)
19 | val activityRule = ActivityScenarioRule(SplashActivity::class.java)
20 |
21 | @get:Rule
22 | var mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA)
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/permissions/ActivityPermissionsChecker.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | import android.content.pm.PackageManager
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.core.app.ActivityCompat
6 |
7 | class ActivityPermissionsChecker(
8 | private val activity: AppCompatActivity,
9 | ) : PermissionsChecker {
10 |
11 | override fun hasPermissions(permission: String): Boolean {
12 | return ActivityCompat.checkSelfPermission(
13 | activity,
14 | permission
15 | ) == PackageManager.PERMISSION_GRANTED
16 | }
17 |
18 | override fun shouldShowRequestPermissionRationale(permission: String): Boolean {
19 | return ActivityCompat.shouldShowRequestPermissionRationale(
20 | activity,
21 | permission
22 | )
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/keyboard_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visibility_off_40dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/presets_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp/keyboard_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp/presets_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp-land/presets_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/customviews/ActionButton.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.customviews
2 |
3 | import android.content.Context
4 | import android.speech.tts.TextToSpeech
5 | import android.util.AttributeSet
6 | import com.willowtree.vocable.utils.VocableTextToSpeech
7 |
8 | /**
9 | * A subclass of VocableButton that allows a caller to define a custom action
10 | */
11 | open class ActionButton @JvmOverloads constructor(
12 | context: Context,
13 | attrs: AttributeSet? = null,
14 | defStyle: Int = 0
15 | ) : VocableButton(context, attrs, defStyle) {
16 |
17 | var action: (() -> Unit)? = null
18 |
19 | override fun performAction() {
20 | action?.invoke()
21 | }
22 |
23 | override fun sayText(text: CharSequence?) {
24 | if (text?.isNotBlank() == true) {
25 | VocableTextToSpeech.speak(locale, text.toString())
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/settings/selectionmode/SelectionModeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.settings.selectionmode
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.asLiveData
5 | import androidx.lifecycle.map
6 | import com.willowtree.vocable.utils.FaceTrackingPermissions
7 | import com.willowtree.vocable.utils.IFaceTrackingPermissions
8 | import com.willowtree.vocable.utils.isEnabled
9 |
10 | class SelectionModeViewModel(
11 | private val faceTrackingPermissions: IFaceTrackingPermissions,
12 | ) : ViewModel() {
13 |
14 | val headTrackingEnabled = faceTrackingPermissions.permissionState.asLiveData().map { it.isEnabled() }
15 |
16 | fun requestHeadTracking() {
17 | faceTrackingPermissions.requestFaceTracking()
18 | }
19 |
20 | fun disableHeadTracking() {
21 | faceTrackingPermissions.disableFaceTracking()
22 | }
23 |
24 | }
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/IVocableSharedPreferences.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import android.content.SharedPreferences
4 |
5 |
6 | interface IVocableSharedPreferences {
7 |
8 | fun registerOnSharedPreferenceChangeListener(vararg listeners: SharedPreferences.OnSharedPreferenceChangeListener)
9 |
10 | fun unregisterOnSharedPreferenceChangeListener(vararg listeners: SharedPreferences.OnSharedPreferenceChangeListener)
11 |
12 | fun getMySayings(): List
13 |
14 | fun setMySayings(mySayings: Set)
15 |
16 | fun getDwellTime(): Long
17 |
18 | fun setDwellTime(time: Long)
19 |
20 | fun getSensitivity(): Float
21 |
22 | fun setSensitivity(sensitivity: Float)
23 |
24 | fun setHeadTrackingEnabled(enabled: Boolean)
25 |
26 | fun getHeadTrackingEnabled(): Boolean
27 |
28 | fun setFirstTime()
29 |
30 | fun getFirstTime(): Boolean
31 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/guidelines_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/ICategoriesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import com.willowtree.vocable.presets.Category
4 | import com.willowtree.vocable.room.CategorySortOrder
5 | import com.willowtree.vocable.utils.locale.LocalesWithText
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface ICategoriesUseCase {
9 | fun categories(): Flow>
10 | suspend fun updateCategoryName(categoryId: String, localizedName: LocalesWithText)
11 | suspend fun addCategory(categoryName: String)
12 | suspend fun updateCategorySortOrders(categorySortOrders: List)
13 | suspend fun getCategoryById(categoryId: String): Category
14 | suspend fun updateCategoryHidden(categoryId: String, hidden: Boolean)
15 | suspend fun deleteCategory(categoryId: String)
16 | suspend fun moveCategoryUp(categoryId: String)
17 | suspend fun moveCategoryDown(categoryId: String)
18 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_dark_blue.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_light_48dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Documentation/ReleaseProcess.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | The release process for this project is a pretty standard one for Android projects. We just use incremental build numbers,
4 | so you'll need to increment the build number and then generate an .aab file. The signing keys are available in LastPass - if you need help finding them, ping Cameron Greene on Slack.
5 | The password for the key and keystore are the same, and the alias is vocable-release.
6 |
7 | Once the .aab is generated, you can upload it to the Play Console. We have been uploading to the internal test track first, and making sure a couple of people
8 | are able to update their apps successfully within WillowTree before promoting it to Production. You can also do one last smoke test
9 | on the release build while it's in internal test, if you'd like.
10 |
11 | Once the release is promoted to Production, keep an eye on Crashlytics for a couple of days to make sure there aren't any major issues that come up.
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp-land/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4
4 | 9
5 | 6
6 | 3
7 | 3
8 | 2
9 | 3
10 | 2
11 | 2
12 | 5
13 | 10
14 | 3
15 | 2
16 | 3
17 | 2
18 | 4
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/LiveDataExt.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.Observer
5 | import java.util.concurrent.CountDownLatch
6 | import java.util.concurrent.TimeUnit
7 | import java.util.concurrent.TimeoutException
8 |
9 | fun LiveData.getOrAwaitValue(
10 | time: Long = 2,
11 | timeUnit: TimeUnit = TimeUnit.SECONDS
12 | ): T {
13 | var data: T? = null
14 | val latch = CountDownLatch(1)
15 | val observer = object : Observer {
16 | override fun onChanged(o: T) {
17 | data = o
18 | latch.countDown()
19 | this@getOrAwaitValue.removeObserver(this)
20 | }
21 | }
22 |
23 | this.observeForever(observer)
24 |
25 | // Don't wait indefinitely if the LiveData is not set.
26 | if (!latch.await(time, timeUnit)) {
27 | throw TimeoutException("LiveData value was never set.")
28 | }
29 |
30 | @Suppress("UNCHECKED_CAST")
31 | return data as T
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/settings/EditCategoryPhrasesViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.settings
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.SavedStateHandle
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.asLiveData
7 | import com.willowtree.vocable.IPhrasesUseCase
8 | import com.willowtree.vocable.presets.Category
9 | import com.willowtree.vocable.presets.Phrase
10 | import com.willowtree.vocable.utils.ILocalizedResourceUtility
11 |
12 | class EditCategoryPhrasesViewModel(
13 | savedStateHandle: SavedStateHandle,
14 | phrasesUseCase: IPhrasesUseCase,
15 | private val localizedResourceUtility: ILocalizedResourceUtility
16 | ) : ViewModel() {
17 |
18 | val categoryPhraseList: LiveData> = phrasesUseCase.getPhrasesForCategoryFlow(savedStateHandle.get("category")!!.categoryId)
19 | .asLiveData()
20 |
21 | fun getCategoryName(category: Category): String {
22 | return localizedResourceUtility.getTextFromCategory(category)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/utils/permissions/ActivityPermissionRegisterForLaunch.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | import androidx.activity.result.contract.ActivityResultContract
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class ActivityPermissionRegisterForLaunch(
7 | private val activity: AppCompatActivity
8 | ) : PermissionRequester {
9 |
10 | override fun registerForActivityResult(
11 | contract: ActivityResultContract,
12 | activityResultCallback: (Boolean) -> Unit,
13 | ): PermissionRequestLauncher {
14 | val activityLauncher = activity.registerForActivityResult(contract) {
15 | activityResultCallback(it)
16 | }
17 | return object : PermissionRequestLauncher {
18 | override fun launch(input: String) {
19 | activityLauncher.launch(input)
20 | }
21 |
22 | override fun unregister() {
23 | activityLauncher.unregister()
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-land/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Q
5 | - W
6 | - E
7 | - R
8 | - T
9 | - Y
10 | - U
11 | - I
12 | - O
13 | - P
14 | - A
15 | - S
16 | - D
17 | - F
18 | - G
19 | - H
20 | - J
21 | - K
22 | - L
23 | - \'
24 | - Z
25 | - X
26 | - C
27 | - V
28 | - B
29 | - N
30 | - M
31 | - ,
32 | - .
33 | - \?
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/settings/EditPhrasesViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.settings
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.willowtree.vocable.PhrasesUseCase
8 | import kotlinx.coroutines.launch
9 | import org.koin.core.component.KoinComponent
10 | import org.koin.core.component.inject
11 |
12 | class EditPhrasesViewModel : ViewModel(), KoinComponent {
13 |
14 | private val phrasesUseCase: PhrasesUseCase by inject()
15 |
16 | private val liveShowPhraseAdded = MutableLiveData()
17 | val showPhraseAdded: LiveData = liveShowPhraseAdded
18 |
19 | fun updatePhrase(phraseId: String, newText: String) {
20 | viewModelScope.launch {
21 | phrasesUseCase.updatePhrase(phraseId, newText)
22 | liveShowPhraseAdded.postValue(true)
23 | }
24 | }
25 |
26 | fun phraseToFalse() {
27 | liveShowPhraseAdded.value = false
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Q
5 | - W
6 | - E
7 | - R
8 | - T
9 | - Y
10 | - U
11 | - I
12 | - O
13 | - P
14 | - A
15 | - S
16 | - D
17 | - F
18 | - G
19 | - H
20 | - J
21 | - K
22 | - L
23 | - \'
24 | - Z
25 | - X
26 | - C
27 | - V
28 | - B
29 | - N
30 | - M
31 | - ,
32 | - .
33 | - \?
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/permissions/FakePermissionRegisterForLaunch.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | import androidx.activity.result.contract.ActivityResultContract
4 |
5 | class FakePermissionRegisterForLaunch : PermissionRequester {
6 |
7 | private var callbacks = mutableMapOf Unit>()
8 |
9 | val launchCount: Int
10 | get() = callbacks.size
11 |
12 | override fun registerForActivityResult(
13 | contract: ActivityResultContract,
14 | activityResultCallback: (Boolean) -> Unit,
15 | ): PermissionRequestLauncher {
16 | return object: PermissionRequestLauncher {
17 | override fun launch(input: String) {
18 | callbacks[input] = activityResultCallback
19 | }
20 |
21 | override fun unregister() {
22 | // no-op
23 | }
24 | }
25 | }
26 |
27 | fun triggerActivityResult(contract: String, result: Boolean) {
28 | callbacks[contract]?.invoke(result)
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/FakeLocalizedResourceUtility.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils
2 |
3 | import com.willowtree.vocable.presets.Category
4 | import com.willowtree.vocable.presets.CustomPhrase
5 | import com.willowtree.vocable.presets.Phrase
6 | import com.willowtree.vocable.presets.PresetPhrase
7 |
8 | class FakeLocalizedResourceUtility : ILocalizedResourceUtility {
9 | override fun getTextFromCategory(category: Category?): String {
10 | return when(category) {
11 | is Category.PresetCategory -> category.categoryId
12 | is Category.Recents -> "Recents"
13 | is Category.StoredCategory -> category.localizedName.localesTextMap.entries.first().value
14 | null -> ""
15 | }
16 | }
17 |
18 | override fun getTextFromPhrase(phrase: Phrase?): String {
19 | return when(phrase) {
20 | is CustomPhrase -> phrase.localizedUtterance?.localesTextMap?.entries?.first()?.value ?: ""
21 | is PresetPhrase -> phrase.phraseId
22 | null -> ""
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/customviews/VocableSwitch.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.customviews
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import com.willowtree.vocable.R
7 | import com.willowtree.vocable.databinding.VocableSwitchLayoutBinding
8 |
9 | class VocableSwitch @JvmOverloads constructor(
10 | context: Context,
11 | attrs: AttributeSet? = null,
12 | defStyle: Int = 0
13 | ) : VocableConstraintLayout(context, attrs, defStyle){
14 | var binding: VocableSwitchLayoutBinding
15 | var isChecked: Boolean
16 | set(value) {
17 | binding.toggleSwitch.isChecked = value
18 | }
19 | get() = binding.toggleSwitch.isChecked
20 |
21 | var text: String = ""
22 | set(value) {
23 | binding.toggleTitle.text = value
24 | }
25 |
26 | init{
27 | binding = VocableSwitchLayoutBinding.inflate(LayoutInflater.from(context), this)
28 | binding.root.setBackgroundResource(R.drawable.settings_group_background)
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/Documentation/Firebase.md:
--------------------------------------------------------------------------------
1 | # Firebase in Vocable
2 |
3 | ### Usage
4 | Vocable is connected to Firebase for the purpose of using Crashlytics and, at some point in the future, Firebase Analytics.
5 | It is currently set up to be able to handle both, but is only using Crashlytics at the moment. The link to the Firebase project is [here](https://console.firebase.google.com/u/0/project/vocable-fcb07/overview).
6 |
7 | ### Structure
8 | The Firebase project is split into three apps - Vocable iOS, Vocable Android, and Vocable Android Debug. For Android, we use the debug
9 | app for debug builds, and the regular app for release/production builds, so that we don't get crash data mixed up in Crashlytics.
10 |
11 | ### Analytics
12 | We are not currently tracking any analytics events, but the project should be set up to handle that, whenever we decide to add analytics events.
13 | At some point, it would be very beneficial to add analytics and/or event tracking, so that we can get a better feel for how users are using certain features,
14 | but that isn't something that we've implemented yet, beyond setting up the framework for it.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # IntelliJ
36 | *.iml
37 | .idea/
38 |
39 | # Keystore files
40 | # Uncomment the following line if you do not want to check your keystore files in.
41 | #*.jks
42 |
43 | # External native build folder generated in Android Studio 2.2 and later
44 | .externalNativeBuild
45 |
46 | # Freeline
47 | freeline.py
48 | freeline/
49 | freeline_project_description.json
50 |
51 | # fastlane
52 | fastlane/report.xml
53 | fastlane/Preview.html
54 | fastlane/screenshots
55 | fastlane/test_output
56 | fastlane/readme.md
57 |
58 | app/keys/
59 | app/release/
60 | app/.DS_Store
61 | .DS_Store
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_custom_category_phrase_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 WillowTree, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/VocableApp.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable
2 |
3 | import android.app.Application
4 | import io.github.inflationx.calligraphy3.CalligraphyConfig
5 | import io.github.inflationx.calligraphy3.CalligraphyInterceptor
6 | import io.github.inflationx.viewpump.ViewPump
7 | import org.koin.android.ext.koin.androidContext
8 | import org.koin.core.context.startKoin
9 | import timber.log.Timber
10 |
11 | class VocableApp : Application() {
12 |
13 | override fun onCreate() {
14 | super.onCreate()
15 | if (BuildConfig.DEBUG) {
16 | Timber.plant(Timber.DebugTree())
17 | }
18 |
19 | ViewPump.builder()
20 | .addInterceptor(
21 | CalligraphyInterceptor(
22 | CalligraphyConfig.Builder()
23 | .setDefaultFontPath("fonts/Roboto-RobotoRegular.ttf")
24 | .build()
25 | )
26 | )
27 | .build()
28 |
29 |
30 | startKoin {
31 | androidContext(this@VocableApp)
32 |
33 | modules(vocableKoinModule)
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-de-land/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Q
5 | - W
6 | - E
7 | - R
8 | - T
9 | - Z
10 | - U
11 | - I
12 | - O
13 | - P
14 | - Ü
15 | - A
16 | - S
17 | - D
18 | - F
19 | - G
20 | - H
21 | - J
22 | - K
23 | - L
24 | - Ö
25 | - Ä
26 | - Y
27 | - X
28 | - C
29 | - V
30 | - B
31 | - N
32 | - M
33 | - ß
34 | - \'
35 | - .
36 | - \?
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de-sw600dp/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Q
5 | - W
6 | - E
7 | - R
8 | - T
9 | - Z
10 | - U
11 | - I
12 | - O
13 | - P
14 | - Ü
15 | - A
16 | - S
17 | - D
18 | - F
19 | - G
20 | - H
21 | - J
22 | - K
23 | - L
24 | - Ö
25 | - Ä
26 | - Y
27 | - X
28 | - C
29 | - V
30 | - B
31 | - N
32 | - M
33 | - ß
34 | - \'
35 | - .
36 | - \?
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PresetCategoryDao.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.Query
6 | import androidx.room.Update
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | @Dao
10 | interface PresetCategoryDao {
11 | @Insert
12 | suspend fun insertPresetCategory(presetCategoryDto: PresetCategoryDto)
13 |
14 | @Update(entity = PresetCategoryDto::class)
15 | suspend fun updateCategorySortOrders(categorySortOrders: List)
16 |
17 | @Update(entity = PresetCategoryDto::class)
18 | suspend fun updateCategoryHidden(categoryHidden: PresetCategoryHidden)
19 |
20 | @Query("SELECT * FROM PresetCategory WHERE category_id = :categoryId")
21 | suspend fun getPresetCategoryById(categoryId: String): PresetCategoryDto?
22 |
23 | @Query("SELECT * FROM PresetCategory WHERE category_id != 'preset_user_favorites'")
24 | fun getAllPresetCategoriesFlow(): Flow>
25 |
26 | @Update(entity = PresetCategoryDto::class)
27 | suspend fun updateCategoryDeleted(categoryDeleted: PresetCategoryDeleted)
28 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test Project
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - '**'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-20.04
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v3
17 |
18 | - name: Set Up JDK 17
19 | uses: actions/setup-java@v3
20 | with:
21 | java-version: '17'
22 | distribution: 'temurin'
23 | cache: gradle
24 |
25 | - name: Run Unit Tests
26 | run: ./gradlew testDebug
27 |
28 | - name: Assemble
29 | env:
30 | VERSION_CODE: ${{ github.run_number }}
31 | run: ./gradlew assembleDebug assembleDebugAndroidTest
32 |
33 | - uses: actions/upload-artifact@v4
34 | with:
35 | name: apk
36 | path: |
37 | app/build/outputs/apk/debug/app-debug.apk
38 | app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
39 |
40 | pre-release-upload:
41 | needs: build
42 | if: github.event_name != 'pull_request'
43 | uses: ./.github/workflows/pre-release-upload.yml
44 | secrets: inherit
--------------------------------------------------------------------------------
/app/src/main/res/values-de/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - A
5 | - B
6 | - C
7 | - D
8 | - E
9 | - F
10 | - G
11 | - H
12 | - I
13 | - J
14 | - K
15 | - L
16 | - M
17 | - N
18 | - O
19 | - P
20 | - Q
21 | - R
22 | - S
23 | - T
24 | - U
25 | - V
26 | - W
27 | - X
28 | - Y
29 | - Z
30 | - Ä
31 | - Ö
32 | - Ü
33 | - ß
34 | - \'
35 | - ,
36 | - .
37 | - !
38 | - \?
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - A
5 | - B
6 | - C
7 | - D
8 | - E
9 | - F
10 | - G
11 | - H
12 | - I
13 | - J
14 | - K
15 | - L
16 | - M
17 | - N
18 | - O
19 | - P
20 | - Q
21 | - R
22 | - S
23 | - T
24 | - U
25 | - V
26 | - W
27 | - X
28 | - Y
29 | - Z
30 | - ,
31 | - .
32 | - \'
33 | - \?
34 | - À
35 | - È
36 | - Ì
37 | - Ò
38 | - Ù
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/presets/PresetCategories.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.presets
2 |
3 | import com.willowtree.vocable.R
4 |
5 | enum class PresetCategories(val id: String, val initialSortOrder: Int) {
6 | GENERAL("preset_general", 0),
7 | BASIC_NEEDS("preset_basic_needs", 1),
8 | PERSONAL_CARE("preset_personal_care", 2),
9 | CONVERSATION("preset_conversation", 3),
10 | ENVIRONMENT("preset_environment", 4 ),
11 | USER_KEYPAD("preset_user_keypad", 5),
12 | RECENTS("preset_recents", 6),
13 | @Deprecated("This is being filtered out from the UI already. Remove this.")
14 | MY_SAYINGS("preset_user_favorites", 7);
15 |
16 | fun getArrayId(): Int {
17 | return when (this) {
18 | GENERAL -> R.array.category_general
19 | BASIC_NEEDS -> R.array.category_basic_needs
20 | CONVERSATION -> R.array.category_conversation
21 | ENVIRONMENT -> R.array.category_environment
22 | PERSONAL_CARE -> R.array.category_personal_care
23 | USER_KEYPAD -> R.array.category_123
24 | RECENTS -> -1
25 | MY_SAYINGS -> -1 // Not localized with same convention
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-land/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2
4 | 6
5 | 2
6 | 3
7 | 2
8 | 3
9 | 2
10 | 10
11 | 3
12 | 6
13 | 2
14 | 2
15 | 2
16 | 2
17 | 10
18 | 3
19 | 1
20 | 2
21 | 2
22 | 3
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2
4 | 10
5 | 4
6 | 2
7 | 5
8 | 3
9 | 2
10 | 10
11 | 3
12 | 3
13 | 4
14 | 2
15 | 2
16 | 9
17 | 10
18 | 3
19 | 1
20 | 5
21 | 2
22 | 8
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw400dp-land/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2
4 | 6
5 | 2
6 | 3
7 | 2
8 | 3
9 | 2
10 | 10
11 | 3
12 | 6
13 | 2
14 | 2
15 | 2
16 | 2
17 | 10
18 | 3
19 | 1
20 | 3
21 | 2
22 | 3
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it-land/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Q
5 | - W
6 | - E
7 | - R
8 | - T
9 | - Y
10 | - U
11 | - I
12 | - O
13 | - P
14 | - À
15 | - È
16 | - A
17 | - S
18 | - D
19 | - F
20 | - G
21 | - H
22 | - J
23 | - K
24 | - L
25 | - ‘
26 | - Ì
27 | - Ò
28 | - Z
29 | - X
30 | - C
31 | - V
32 | - B
33 | - N
34 | - M
35 | - ,
36 | - .
37 | - \?
38 | - !
39 | - Ù
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/vocable_switch_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
26 |
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/utility/VocableKoinTestRule.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utility
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import com.willowtree.vocable.utils.VocableSharedPreferences
5 | import com.willowtree.vocable.vocableKoinModule
6 | import org.junit.rules.TestWatcher
7 | import org.junit.runner.Description
8 | import org.koin.android.ext.koin.androidContext
9 | import org.koin.core.context.startKoin
10 | import org.koin.core.context.stopKoin
11 | import org.koin.core.module.Module
12 |
13 | class VocableKoinTestRule(
14 | private vararg val additionalTestModules: Module
15 | ): TestWatcher() {
16 | override fun starting(description: Description?) {
17 | startKoin {
18 | androidContext(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext)
19 | modules(
20 | vocableKoinModule +
21 | vocableTestModule +
22 | additionalTestModules.toList()
23 | )
24 | }.koin.apply {
25 | get().clearAll()
26 | }
27 | }
28 |
29 | override fun finished(description: Description?) {
30 | stopKoin()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_shown.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/willowtree/vocable/utils/permissions/FakePermissionsRationaleDialogShower.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.utils.permissions
2 |
3 | class FakePermissionsRationaleDialogShower : PermissionsRationaleDialogShower {
4 |
5 | var rationaleDialogShowedCount = 0
6 | private set
7 |
8 | var settingsRationaleDialogShowedCount = 0
9 | private set
10 |
11 | var rationaleDialogOnPositiveClick : () -> Unit = {}
12 | private set
13 |
14 | var rationaleDialogOnNegativeClick : () -> Unit = {}
15 | private set
16 |
17 | var settingsRationaleDialogOnPositiveClick : () -> Unit = {}
18 | private set
19 |
20 | override fun showPermissionRationaleDialog(
21 | onPositiveClick: () -> Unit,
22 | onNegativeClick: () -> Unit,
23 | onDismiss: () -> Unit,
24 | ) {
25 | rationaleDialogShowedCount++
26 | rationaleDialogOnPositiveClick = onPositiveClick
27 | rationaleDialogOnNegativeClick = onNegativeClick
28 | }
29 |
30 | override fun showSettingsPermissionRationaleDialog(
31 | onPositiveClick: () -> Unit,
32 | onNegativeClick: () -> Unit,
33 | ) {
34 | settingsRationaleDialogShowedCount++
35 | settingsRationaleDialogOnPositiveClick = onPositiveClick
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1
4 | 8
5 | 3
6 | 2
7 | 4
8 | 1
9 | 4
10 | 5
11 | 1
12 | 5
13 | 6
14 | 3
15 | 4
16 | 2
17 | 1
18 | 4
19 | 5
20 | 6
21 | 1
22 | 6
23 | 1
24 | 8
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw400dp/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1
4 | 8
5 | 3
6 | 2
7 | 4
8 | 1
9 | 4
10 | 5
11 | 1
12 | 5
13 | 6
14 | 3
15 | 4
16 | 2
17 | 1
18 | 4
19 | 5
20 | 6
21 | 1
22 | 9
23 | 1
24 | 8
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/splash/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.splash
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.willowtree.vocable.room.RoomPresetPhrasesRepository
8 | import com.willowtree.vocable.utils.IdlingResourceContainer
9 | import com.willowtree.vocable.utils.VocableSharedPreferences
10 | import kotlinx.coroutines.launch
11 |
12 | class SplashViewModel(
13 | private val newPresetsRepository: RoomPresetPhrasesRepository,
14 | private val sharedPrefs: VocableSharedPreferences,
15 | private val idlingResourceContainer: IdlingResourceContainer
16 | ) : ViewModel() {
17 |
18 | private val liveExitSplash = MutableLiveData(false)
19 | val exitSplash: LiveData = liveExitSplash
20 |
21 | init {
22 | populateDatabase()
23 | }
24 |
25 | private fun populateDatabase() {
26 | viewModelScope.launch {
27 | idlingResourceContainer.run {
28 | if (sharedPrefs.getFirstTime()) {
29 | newPresetsRepository.populateDatabase()
30 | sharedPrefs.setFirstTime()
31 | }
32 |
33 | liveExitSplash.postValue(true)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/error_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
26 |
27 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Vocable Contributing Guidelines
2 |
3 | 🥳🎉 Thank you for contributing to Vocable! 🎉🥳
4 |
5 | Contributions from the open-source community are crucial in helping us meet make the best AAC app possible.
6 |
7 | # How to Contribute
8 |
9 | ### Reporting Bugs
10 | - Check the existing issues to make sure the bug has not already been reported.
11 | - Open an issue describing the bug.
12 | - Be sure to use a descriptive title and include detailed steps on how to reproduce the issue.
13 | - Explain the expected behavior vs. the actual behavior.
14 | - Include the version number.
15 | - Tag the issue with the `bug` label.
16 |
17 |
18 | ### Suggesting Enhancements
19 | - Open an issue clearly describing the behavior you would like to see.
20 | - If applicable, provide a few examples of how the feature would work and what purpose it would serve.
21 | - Tag the issue with the `enhancement` label.
22 |
23 | ### Contributing Code by Opening a Pull Request
24 | To contribute to Vocable, please fork the GitHub repo and submit a pull request to the `develop` branch.
25 |
26 | Our code owners will review your work and merge once any issues have been addressed. If the PR gets too outdated we may ask you to rebase and force push to update the PR.
27 |
28 | That's it! Our team will merge your PR, and your changes will be included in the next app version. Thanks for your contribution! 💯
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp/my_sayings_empty_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/presets/RoomPresetCategoriesRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.presets
2 |
3 | import androidx.room.Room
4 | import androidx.test.core.app.ApplicationProvider
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.willowtree.vocable.room.VocableDatabase
7 | import kotlinx.coroutines.flow.first
8 | import kotlinx.coroutines.test.runTest
9 | import org.junit.Assert.assertEquals
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 |
13 | @RunWith(AndroidJUnit4::class)
14 | class RoomPresetCategoriesRepositoryTest {
15 | private fun createRepository(): RoomPresetCategoriesRepository {
16 | return RoomPresetCategoriesRepository(Room.inMemoryDatabaseBuilder(
17 | ApplicationProvider.getApplicationContext(),
18 | VocableDatabase::class.java
19 | ).build())
20 | }
21 |
22 | @Test
23 | fun preset_categories_returned() = runTest {
24 | val repository = createRepository()
25 |
26 | assertEquals(
27 | PresetCategories.values().filter { it != PresetCategories.MY_SAYINGS }.map {
28 | Category.PresetCategory(
29 | it.id,
30 | it.initialSortOrder,
31 | false,
32 | )
33 | },
34 | repository.getPresetCategories().first()
35 | )
36 | }
37 | }
--------------------------------------------------------------------------------
/.github/workflows/metrics.yml:
--------------------------------------------------------------------------------
1 | name: Monthly issue metrics
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '3 2 1 * *'
6 |
7 | permissions:
8 | issues: write
9 | pull-requests: read
10 |
11 | jobs:
12 | build:
13 | name: issue metrics
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 |
18 | - name: Get dates for last month
19 | shell: bash
20 | run: |
21 | # Calculate the first day of the previous month
22 | first_day=$(date -d "last month" +%Y-%m-01)
23 |
24 | # Calculate the last day of the previous month
25 | last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
26 |
27 | #Set an environment variable with the date range
28 | echo "$first_day..$last_day"
29 | echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
30 |
31 | - name: Run issue-metrics tool
32 | uses: github/issue-metrics@v2
33 | env:
34 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 | SEARCH_QUERY: 'repo:willowtreeapps/vocable-android is:pr created:${{ env.last_month }}'
36 |
37 | - name: Create issue
38 | uses: peter-evans/create-issue-from-file@v4
39 | with:
40 | title: Monthly issue metrics report
41 | token: ${{ secrets.GITHUB_TOKEN }}
42 | content-filepath: ./issue_metrics.md
43 | assignees: PaulKlauser
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/presets/ILegacyCategoriesAndPhrasesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.presets
2 |
3 | import com.willowtree.vocable.room.CategoryDto
4 | import com.willowtree.vocable.room.CategorySortOrder
5 | import com.willowtree.vocable.room.PhraseDto
6 | import com.willowtree.vocable.utils.locale.LocalesWithText
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | @Deprecated("This is the old way of accessing categories and phrases. Prefer using" +
10 | " ICategoriesUseCase and PhrasesUseCase instead.")
11 | interface ILegacyCategoriesAndPhrasesRepository {
12 | suspend fun getPhrasesForCategory(categoryId: String): List
13 |
14 | /**
15 | * Return all categories, sorted by [CategoryDto.sortOrder]
16 | */
17 | fun getAllCategoriesFlow(): Flow>
18 |
19 | /**
20 | * Return all categories, sorted by [CategoryDto.sortOrder]
21 | */
22 | suspend fun getAllCategories(): List
23 | suspend fun updateCategorySortOrders(categorySortOrders: List)
24 | suspend fun updateCategoryName(categoryId: String, localizedName: LocalesWithText)
25 | suspend fun updateCategoryHidden(categoryId: String, hidden: Boolean)
26 | suspend fun deleteCategory(categoryId: String)
27 | suspend fun getRecentPhrases(): List
28 | suspend fun deletePhrases(phrases: List)
29 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/presets_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/my_sayings_empty_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
27 |
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | # JSR 305 annotations are for embedding nullability information.
23 |
24 | # Keep room model class members
25 | -keepclassmembers class com.willowtree.vocable.room.models** {
26 | (...);
27 | ;
28 | }
29 |
30 | -keepnames class * extends android.os.Parcelable
31 |
32 | -dontwarn com.google.ar.sceneform.animation.AnimationEngine
33 | -dontwarn com.google.ar.sceneform.animation.AnimationLibraryLoader
34 | -dontwarn com.google.ar.sceneform.assets.Loader
35 | -dontwarn com.google.ar.sceneform.assets.ModelData
36 | -dontwarn com.google.devtools.build.android.desugar.runtime.ThrowableExtension
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyboard_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/screens/KeyboardScreen.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.screens
2 |
3 | import androidx.test.espresso.Espresso.onView
4 | import androidx.test.espresso.NoMatchingViewException
5 | import androidx.test.espresso.matcher.ViewMatchers.withId
6 | import androidx.test.espresso.matcher.ViewMatchers.withText
7 | import com.willowtree.vocable.R
8 | import com.willowtree.vocable.utility.tap
9 |
10 | class KeyboardScreen {
11 |
12 | // Keyboard
13 | val keyboardInputText = onView(withId(R.id.keyboard_input))
14 | val keyboardCont = onView(withId(R.id.keyboard_key_holder))
15 | val keyboardSpaceButton = onView(withId(R.id.keyboard_space_button))
16 | val keyboardBackspaceButton = onView(withId(R.id.keyboard_backspace_button))
17 | val keyboardClearButton = onView(withId(R.id.keyboard_clear_button))
18 | val keyboardPresetsButton = onView(withId(R.id.presets_button))
19 |
20 | fun tapSeveralLetters(letters: String) {
21 | val numLetters = letters.length
22 |
23 | for (i in 1..numLetters) {
24 | try {
25 | if(letters[i-1] == ' ')
26 | keyboardSpaceButton.tap()
27 | else
28 | onView(withText(letters[i - 1].toString().uppercase())).tap()
29 | } catch (e: NoMatchingViewException) {
30 | //do nothing if the character isn't one we support
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/my_sayings_empty_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.OnConflictStrategy
7 | import androidx.room.Query
8 | import androidx.room.Update
9 | import kotlinx.coroutines.flow.Flow
10 |
11 | @Dao
12 | interface PhraseDao {
13 |
14 | @Insert(onConflict = OnConflictStrategy.REPLACE)
15 | suspend fun insertPhrase(phrase: PhraseDto)
16 |
17 | @Insert(onConflict = OnConflictStrategy.REPLACE)
18 | suspend fun insertPhrases(vararg phrases: PhraseDto)
19 |
20 | @Query("DELETE FROM Phrase WHERE phrase_id == :phraseId")
21 | suspend fun deletePhrase(phraseId: String)
22 |
23 | @Delete
24 | suspend fun deletePhrases(vararg phrases: PhraseDto)
25 |
26 | @Update(entity = PhraseDto::class)
27 | suspend fun updatePhraseSpokenDate(phraseSpokenDate: PhraseSpokenDate)
28 |
29 | @Update(entity = PhraseDto::class)
30 | suspend fun updatePhraseLocalizedUtterance(phraseLocalizedUtterance: PhraseLocalizedUtterance)
31 |
32 | @Query("SELECT * FROM Phrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8")
33 | fun getRecentPhrases(): Flow>
34 |
35 | @Query("SELECT * FROM Phrase WHERE parent_category_id == :categoryId")
36 | fun getPhrasesForCategory(categoryId: String): Flow>
37 |
38 | @Query("SELECT * FROM Phrase WHERE phrase_id == :phraseId")
39 | suspend fun getPhrase(phraseId: String): PhraseDto?
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/willowtree/vocable/room/RoomStoredCategoriesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.room
2 |
3 | import com.willowtree.vocable.presets.Category
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | class RoomStoredCategoriesRepository(
7 | private val database: VocableDatabase
8 | ) : StoredCategoriesRepository {
9 |
10 | override fun getAllCategories(): Flow> {
11 | return database.categoryDao().getAllCategoriesFlow()
12 | }
13 |
14 | override suspend fun upsertCategory(category: Category.StoredCategory) {
15 | database.categoryDao().insertCategory(
16 | CategoryDto(
17 | category.categoryId,
18 | 0L,
19 | category.localizedName,
20 | category.hidden,
21 | category.sortOrder
22 | )
23 | )
24 | }
25 |
26 | override suspend fun updateCategorySortOrders(categorySortOrders: List) {
27 | database.categoryDao().updateCategorySortOrders(categorySortOrders)
28 | }
29 |
30 | override suspend fun getCategoryById(categoryId: String): CategoryDto? =
31 | database.categoryDao().getCategoryById(categoryId)
32 |
33 | override suspend fun updateCategoryHidden(categoryId: String, hidden: Boolean) {
34 | database.categoryDao().updateCategoryHidden(StoredCategoryHidden(categoryId, hidden))
35 | }
36 |
37 | override suspend fun deleteCategory(categoryId: String) {
38 | database.categoryDao().deleteCategory(categoryId)
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/edit_custom_category_phrase_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/edit_custom_category_phrase_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_hidden.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/willowtree/vocable/tests/MainScreenTest.kt:
--------------------------------------------------------------------------------
1 | package com.willowtree.vocable.tests
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.willowtree.vocable.screens.MainScreen
5 | import com.willowtree.vocable.utility.assertTextMatches
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | @RunWith(AndroidJUnit4::class)
10 | class MainScreenTest : BaseTest() {
11 |
12 | private val mainScreen = MainScreen()
13 |
14 | @Test
15 | fun verifyDefaultTextAppears() {
16 | mainScreen.apply {
17 | val defaultText = "Select something below to speak."
18 | currentText.assertTextMatches(defaultText)
19 | }
20 | }
21 |
22 | @Test
23 | fun verifyClickingPhraseUpdatesCurrentText() {
24 | mainScreen.apply {
25 | tapPhrase(defaultPhraseGeneral[0])
26 | currentText.assertTextMatches(defaultPhraseGeneral[0])
27 | }
28 | }
29 |
30 | @Test
31 | fun verifyDefaultCategoriesExist() {
32 | mainScreen.apply {
33 | verifyDefaultCategoriesExist()
34 | }
35 | }
36 |
37 | @Test
38 | fun verifyDefaultSayingsInCategoriesExist() {
39 | mainScreen.apply {
40 | scrollRightAndTapCurrentCategory(0)
41 | verifyGivenPhrasesDisplay(defaultPhraseGeneral)
42 | }
43 | }
44 |
45 | @Test
46 | fun verifySelectingCategoryChangesPhrases() {
47 | mainScreen.apply {
48 | scrollRightAndTapCurrentCategory(1)
49 | verifyGivenPhrasesDisplay(defaultPhraseBasicNeeds)
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
22 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_options_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
29 |
30 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/edit_phrases_action_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/edit_custom_category_phrase_action_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------