├── 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 | --------------------------------------------------------------------------------