├── .bundle └── config ├── .eslintrc.js ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc.js ├── .vscode ├── launch.json └── settings.json ├── .watchmanconfig ├── .yarnrc.yml ├── App.test.tsx ├── App.tsx ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── SECURITY.md ├── android ├── README.md ├── app │ ├── build.gradle │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── com │ │ │ └── scalepractice │ │ │ ├── MainActivity.kt │ │ │ └── MainApplication.kt │ │ └── res │ │ ├── drawable │ │ └── rn_edit_text_material.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── babel.config.js ├── fastlane ├── Appfile ├── Deliverfile ├── Fastfile └── metadata │ ├── android │ ├── de │ │ ├── full_description.txt │ │ └── short_description.txt │ └── en-US │ │ ├── full_description.txt │ │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ └── 04.jpg │ │ └── short_description.txt │ └── ios │ ├── copyright.txt │ ├── en-US │ ├── apple_tv_privacy_policy.txt │ ├── description.txt │ ├── keywords.txt │ ├── marketing_url.txt │ ├── name.txt │ ├── privacy_url.txt │ ├── promotional_text.txt │ ├── release_notes.txt │ ├── subtitle.txt │ └── support_url.txt │ ├── primary_category.txt │ ├── primary_first_sub_category.txt │ ├── primary_second_sub_category.txt │ ├── review_information │ ├── demo_password.txt │ ├── demo_user.txt │ ├── email_address.txt │ ├── first_name.txt │ ├── last_name.txt │ ├── notes.txt │ └── phone_number.txt │ ├── screenshots │ └── README.txt │ ├── secondary_category.txt │ ├── secondary_first_sub_category.txt │ └── secondary_second_sub_category.txt ├── img ├── ANPLogo.png ├── Amazon.png ├── Apple.png ├── BrassRoutinesIcon.png ├── Fdroid.png ├── Google.png ├── arpeggios │ ├── 24.png │ ├── 25.png │ ├── 26.png │ ├── 27.png │ ├── 28.png │ ├── 29.png │ ├── 30.png │ ├── 31.png │ ├── 32.png │ ├── 33.png │ └── 34.png └── scales │ ├── 0.png │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 20.png │ ├── 21.png │ ├── 22.png │ ├── 23.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── index.ts ├── ios ├── .xcode.env ├── Podfile ├── Podfile.lock ├── PrivacyInfo.xcprivacy ├── ScalePractice.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── ScalePractice.xcscheme ├── ScalePractice.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── ScalePractice │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon-1024.png │ │ │ ├── AppIcon-20.png │ │ │ ├── AppIcon-20@2x-1.png │ │ │ ├── AppIcon-20@2x.png │ │ │ ├── AppIcon-20@3x.png │ │ │ ├── AppIcon-29.png │ │ │ ├── AppIcon-29@2x-1.png │ │ │ ├── AppIcon-29@2x.png │ │ │ ├── AppIcon-29@3x.png │ │ │ ├── AppIcon-40.png │ │ │ ├── AppIcon-40@2x-1.png │ │ │ ├── AppIcon-40@2x.png │ │ │ ├── AppIcon-40@3x.png │ │ │ ├── AppIcon-60@2x.png │ │ │ ├── AppIcon-60@3x.png │ │ │ ├── AppIcon-76.png │ │ │ ├── AppIcon-76@2x.png │ │ │ ├── AppIcon-83.5@2x.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ ├── en.lproj │ │ └── LaunchScreen.storyboard │ └── main.m ├── ScalePracticeTests │ ├── Info.plist │ └── ScalePracticeTests.m ├── ScalePracticeWatch Extension │ ├── Assets.xcassets │ │ ├── Complication.complicationset │ │ │ ├── Circular.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-4.png │ │ │ │ ├── output-onlinepngtools-5.png │ │ │ │ ├── output-onlinepngtools-6.png │ │ │ │ └── output-onlinepngtools-7.png │ │ │ ├── Contents.json │ │ │ ├── Extra Large.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-2.png │ │ │ │ ├── output-onlinepngtools-3.png │ │ │ │ ├── output-onlinepngtools-4.png │ │ │ │ └── output-onlinepngtools.png │ │ │ ├── Graphic Bezel.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-7.png │ │ │ │ └── output-onlinepngtools-8.png │ │ │ ├── Graphic Circular.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-7.png │ │ │ │ └── output-onlinepngtools-8.png │ │ │ ├── Graphic Corner.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-2.png │ │ │ │ └── output-onlinepngtools.png │ │ │ ├── Graphic Extra Large.imageset │ │ │ │ └── Contents.json │ │ │ ├── Graphic Large Rectangular.imageset │ │ │ │ └── Contents.json │ │ │ ├── Modular.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-4.png │ │ │ │ ├── output-onlinepngtools-5.png │ │ │ │ ├── output-onlinepngtools-6.png │ │ │ │ └── output-onlinepngtools-7.png │ │ │ └── Utilitarian.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── output-onlinepngtools-2.png │ │ │ │ ├── output-onlinepngtools-3.png │ │ │ │ ├── output-onlinepngtools-4.png │ │ │ │ └── output-onlinepngtools.png │ │ └── Contents.json │ ├── ComplicationController.swift │ ├── ContentView.swift │ ├── ExtensionDelegate.swift │ ├── HostingController.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── PushNotificationPayload.apns ├── ScalePracticeWatch │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon-1024-1024@1x.png │ │ │ ├── AppIcon-1024-108@2x.png │ │ │ ├── AppIcon-1024-24@2x.png │ │ │ ├── AppIcon-1024-24@3x.png │ │ │ ├── AppIcon-1024-27-5@2x-1.png │ │ │ ├── AppIcon-1024-29@2x.png │ │ │ ├── AppIcon-1024-40@2x.png │ │ │ ├── AppIcon-1024-44@2x-1.png │ │ │ ├── AppIcon-1024-50@2x.png │ │ │ ├── AppIcon-1024-86@2x.png │ │ │ ├── AppIcon-1024-98@2x.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Interface.storyboard │ └── Info.plist ├── en.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── es-419.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── fr.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── ja.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── ko.lproj │ ├── InfoPlist.strings │ └── Localizable.strings └── zh-Hans.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── jest.config.ts ├── jest ├── MockContext.js └── setup.js ├── metro.config.js ├── package.json ├── scripts ├── generateComponent.sh ├── hooks │ └── pre-commit └── reset.sh ├── src ├── Components │ ├── AddToListButton │ │ ├── AddToListButton.test.tsx │ │ ├── AddToListButton.tsx │ │ └── index.ts │ ├── AllScalesButton │ │ ├── AllScalesButton.test.tsx │ │ ├── AllScalesButton.tsx │ │ └── index.ts │ ├── HeaderButton │ │ ├── HeaderButton.test.tsx │ │ ├── HeaderButton.tsx │ │ └── index.ts │ ├── LargeScaleDisplay │ │ ├── LargeScaleDisplay.test.tsx │ │ ├── LargeScaleDisplay.tsx │ │ └── index.ts │ ├── ListItems │ │ ├── FlatListItem │ │ │ ├── FlatListItem.test.tsx │ │ │ ├── FlatListItem.tsx │ │ │ └── index.ts │ │ ├── InternalListItem │ │ │ ├── InternalListItem.test.tsx │ │ │ ├── InternalListItem.tsx │ │ │ └── index.ts │ │ ├── LinkListItem │ │ │ ├── LinkListItem.test.tsx │ │ │ ├── LinkListItem.tsx │ │ │ └── index.ts │ │ ├── SwitchListItem │ │ │ ├── SwitchListItem.test.tsx │ │ │ ├── SwitchListItem.tsx │ │ │ └── index.ts │ │ └── TextListItem │ │ │ ├── TextListItem.test.tsx │ │ │ ├── TextListItem.tsx │ │ │ └── index.ts │ ├── RandomizeButton │ │ ├── RandomizeButton.test.tsx │ │ ├── RandomizeButton.tsx │ │ └── index.ts │ ├── ResetButton │ │ ├── ResetButton.test.tsx │ │ ├── ResetButton.tsx │ │ └── index.ts │ ├── ScaleDisplay │ │ ├── ScaleDisplay.test.tsx │ │ ├── ScaleDisplay.tsx │ │ └── index.ts │ ├── ScalePickers │ │ ├── ScalePickers.android.tsx │ │ ├── ScalePickers.ios.tsx │ │ ├── ScalePickers.test.tsx │ │ └── index.ts │ ├── SwipeableRow │ │ ├── SwipeableRow.test.tsx │ │ ├── SwipeableRow.tsx │ │ └── index.ts │ └── SwitchRow │ │ ├── SwitchRow.test.tsx │ │ ├── SwitchRow.tsx │ │ └── index.ts ├── Model │ ├── AcknowledgementsModel.ts │ ├── Model.d.ts │ ├── Model.test.ts │ ├── Model.ts │ ├── MoreModel.ts │ ├── Preferences.tsx │ └── Statistics.tsx ├── Navigation │ ├── AdvancedStack.tsx │ ├── MoreStack.tsx │ ├── RandomStack.tsx │ └── ResourcesStack.tsx ├── Screens │ ├── Acknowledgements │ │ ├── Acknowledgements.test.tsx │ │ └── Acknowledgements.tsx │ ├── Advanced │ │ ├── Advanced.test.tsx │ │ ├── Advanced.tsx │ │ └── utils │ │ │ └── getAdvancedReducer │ │ │ ├── getAdvancedReducer.test.ts │ │ │ └── index.ts │ ├── Help │ │ ├── Help.test.tsx │ │ ├── Help.tsx │ │ └── index.ts │ ├── Licenses │ │ ├── Components │ │ │ ├── LicensesLink │ │ │ │ ├── LicensesLink.tsx │ │ │ │ └── index.ts │ │ │ ├── LicensesList │ │ │ │ ├── LicensesList.test.tsx │ │ │ │ ├── LicensesList.tsx │ │ │ │ └── index.ts │ │ │ └── LicensesListItem │ │ │ │ ├── LicensesListItem.test.tsx │ │ │ │ ├── LicensesListItem.tsx │ │ │ │ └── index.ts │ │ ├── Licenses.test.tsx │ │ ├── Licenses.tsx │ │ ├── licenseData.ts │ │ ├── licenses.json │ │ └── utils │ │ │ ├── extractNameFromGithubUrl │ │ │ └── index.ts │ │ │ └── sortDataByKey │ │ │ └── index.ts │ ├── More │ │ ├── More.test.tsx │ │ └── More.tsx │ ├── Random │ │ ├── Components │ │ │ └── RandomSettings │ │ │ │ ├── RandomSettings.test.tsx │ │ │ │ ├── RandomSettings.tsx │ │ │ │ └── index.ts │ │ ├── Random.d.ts │ │ ├── Random.test.tsx │ │ ├── Random.tsx │ │ ├── enums │ │ │ └── randomActions.ts │ │ ├── index.ts │ │ └── utils │ │ │ ├── getAllArpeggiosFromState │ │ │ └── index.ts │ │ │ ├── getAllScalesFromState │ │ │ └── index.ts │ │ │ ├── getRandomReducer │ │ │ ├── getRandomReducer.test.ts │ │ │ └── index.ts │ │ │ └── getTranslationKeyFromStateKey │ │ │ └── index.ts │ ├── Resources │ │ ├── Resources.test.tsx │ │ └── Resources.tsx │ ├── ScaleDetail │ │ ├── ScaleDetail.test.tsx │ │ └── ScaleDetail.tsx │ └── Statistics │ │ ├── Statistics.tsx │ │ ├── index.ts │ │ └── utils │ │ └── formatStatisticsDataForList │ │ └── index.tsx ├── Translations │ ├── TranslationModel.ts │ ├── Translations.test.ts │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── ja.json │ ├── ko.json │ └── zh.json ├── enums │ ├── appDataTypes.ts │ └── storageKeys.ts └── utils │ ├── capitalize │ ├── capitalize.test.ts │ └── capitalize.ts │ ├── createArpeggioArrayFromParts │ └── index.ts │ ├── createScaleArrayFromParts │ └── index.ts │ ├── getIsSmallScreen │ └── getIsSmallScreen.ts │ ├── getTabBarIcon │ └── index.tsx │ ├── index.ts │ ├── loadFromStorage │ └── index.ts │ ├── random │ └── random.ts │ ├── saveToStorage │ └── index.ts │ ├── shuffle │ └── shuffle.ts │ ├── useDarkMode │ └── index.ts │ └── useIdleScreen │ └── useIdleScreen.ts ├── tsconfig.json ├── tsconfig.spec.json └── yarn.lock /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | env: { 5 | browser: true, 6 | es6: true, 7 | node: true, 8 | }, 9 | extends: ['eslint:recommended', 'plugin:react/recommended', '@react-native'], 10 | ignorePatterns: ['jest/*', '*.test.js'], 11 | globals: { 12 | Atomics: 'readonly', 13 | SharedArrayBuffer: 'readonly', 14 | }, 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | ecmaVersion: 2018, 20 | sourceType: 'module', 21 | }, 22 | plugins: ['react'], 23 | rules: { 24 | eqeqeq: 'off', 25 | 'consistent-this': 'off', 26 | 'react/prop-types': 'error', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '42 15 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | **/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | **/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | /coverage 67 | 68 | # Yarn 69 | .yarn/* 70 | !.yarn/patches 71 | !.yarn/plugins 72 | !.yarn/releases 73 | !.yarn/sdks 74 | !.yarn/versions 75 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | printWidth: 80, 4 | arrowParens: 'always', 5 | semi: true, 6 | tabWidth: 2, 7 | singleQuote: true, 8 | }; 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest Tests", 8 | "program": "${workspaceRoot}/node_modules/jest/bin/jest.js", 9 | "args": ["-i"], 10 | "internalConsoleOptions": "openOnSessionStart", 11 | "outFiles": ["${workspaceRoot}/dist/**/*"], 12 | "envFile": "${workspaceRoot}/.env" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | # yarnPath: .yarn/releases/yarn-3.6.4.cjs 4 | -------------------------------------------------------------------------------- /App.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from './App'; 8 | 9 | import { render } from '@testing-library/react-native'; 10 | 11 | it('renders correctly', () => { 12 | render(); 13 | }); 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexander Burdiss 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Scale Practice 2 | 3 | # Scale Practice 4 | 5 | Apple App Store Badge 6 | Google Play Store Badge 7 | Amazon App Store Badge 8 | F-Droid App Store Badge 9 | 10 | A React Native app available on the Apple App Store, Google Play Store, F-Droid, Amazon App Store and IzzyOnDroid F-Droid compatible repo. You can also download the latest app APK directly from Github. This app helps musicians practice their scales, by randomizing the order of scales and providing resources to learn about new scales. 11 | 12 | ## Requirements 13 | 14 | - iOS 12.4+ 15 | - Android SDK 21 (Android 5.0 Lollipop) 16 | 17 | ## Contributing 18 | 19 | If you have feature requests or bug reports, feel free to help out by sending pull requests or by [creating new issues](https://github.com/aburdiss/ScalePractice/issues/new). 20 | 21 | ## License 22 | 23 | "Scale Practice" is released under the MIT license. See [LICENSE](LICENSE) for details. 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest version of the Application available on Google Play, Amazon App Store, or Apple App Store are supported. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report any security vulnerabilities to [scalepracticesecurity@alexanderburdiss.com](mailto:scalepracticesecurity@alexanderburdiss.com) 10 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | # Releasing a new Android version 2 | 3 | - Update the version number in android/app/build.gradle 4 | - Test the version on a simulator 5 | - `npx react-native run-android --mode release` 6 | - Create the Google Play Build 7 | - `./gradlew bundleRelease` 8 | - outputs to android/app/build/outputs/bundle/release/app-release.aab 9 | - Create the Amazon App Store Build 10 | - `./gradlew assembleRelease` 11 | - outputs to android/app/build/outputs/apk/release/app-release.apk 12 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/java/com/scalepractice/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.scalepractice 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "ScalePractice" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/scalepractice/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.scalepractice 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List = 20 | PackageList(this).packages.apply { 21 | // Packages that cannot be autolinked yet can be added manually here, for example: 22 | // add(MyReactNativePackage()) 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, OpenSourceMergedSoMapping) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 20 | 21 | 28 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #7200FF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Scale Practice 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "35.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 35 6 | targetSdkVersion = 34 7 | ndkVersion = "26.1.10909125" 8 | kotlinVersion = "1.9.24" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.125.0 29 | # Use this property to specify which architecture you want to build. 30 | # You can also override it from the CLI using 31 | # ./gradlew -PreactNativeArchitectures=x86_64 32 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 33 | # Use this property to enable support to the new architecture. 34 | # This will allow you to use TurboModules and the Fabric render in 35 | # your application. You should enable this flag either if you want 36 | # to write custom TurboModules/Fabric components OR use libraries that 37 | # are providing them. 38 | newArchEnabled=true 39 | 40 | # Use this property to enable or disable the Hermes JS engine. 41 | # If set to false, you will be using JSC instead. 42 | hermesEnabled=true 43 | 44 | MYAPP_UPLOAD_STORE_FILE=scalepractice.keystore 45 | MYAPP_UPLOAD_KEY_ALIAS=scalepractice 46 | MYAPP_UPLOAD_STORE_PASSWORD=Password1 47 | MYAPP_UPLOAD_KEY_PASSWORD=Password1 48 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'ScalePractice' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | plugins: [ 4 | [ 5 | 'module-resolver', 6 | { 7 | extensions: [ 8 | '.js', 9 | '.jsx', 10 | '.ts', 11 | '.tsx', 12 | '.android.js', 13 | '.android.tsx', 14 | '.ios.js', 15 | '.ios.tsx', 16 | ], 17 | root: ['.'], 18 | }, 19 | ], 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier("com.AlexanderBurdiss.ScalePractice") # The bundle identifier of your app 2 | apple_id("aburdiss@icloud.com") # Your Apple Developer Portal username 3 | 4 | itc_team_id("120990951") # App Store Connect Team ID 5 | team_id("L4SN53B3F6") # Developer Portal Team ID 6 | 7 | # For more information about the Appfile, see: 8 | # https://docs.fastlane.tools/advanced/#appfile 9 | -------------------------------------------------------------------------------- /fastlane/Deliverfile: -------------------------------------------------------------------------------- 1 | # The Deliverfile allows you to store various App Store Connect metadata 2 | # For more information, check out the docs 3 | # https://docs.fastlane.tools/actions/deliver/ 4 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | fastlane_version '2.210.1' 17 | 18 | before_all do 19 | ensure_git_branch 20 | ensure_git_status_clean 21 | git_pull 22 | end 23 | 24 | platform :ios do 25 | desc "Push a new release build to the App Store" 26 | lane :release do 27 | increment_build_number(xcodeproj: "ScalePractice.xcodeproj") 28 | build_app(workspace: "ScalePractice.xcworkspace", scheme: "ScalePractice") 29 | upload_to_app_store 30 | end 31 | desc 'Build the iOS application.' 32 | private_lane :build do 33 | certificates 34 | increment_build_number(xcodeproj: './ios/name.xcodeproj') 35 | gym(scheme: 'name', project: './ios/name.xcodeproj') 36 | end 37 | end 38 | 39 | platform :android do 40 | desc 'Build the Android application.' 41 | private_lane :build do 42 | gradle(task: 'clean', project_dir: 'android/') 43 | gradle(task: 'assemble', build_type: 'Release', project_dir: 'android/') 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /fastlane/metadata/android/de/full_description.txt: -------------------------------------------------------------------------------- 1 | Scale Practice hilft Musikern, ihre Tonleitern zu üben, indem es die Reihenfolge der Tonleitern zufällig bestimmt und Ressourcen zum Erlernen neuer Tonleitern bereitstellt. Sie enthält drei verschiedene Funktionen: 2 | 3 | 1. Zufällige Tonleiter, ausgewählt aus einer Liste von Tonleiterntypen 4 | 2. Zufällige Tonleiter, ausgewählt aus einer Liste von vom Benutzer eingegebenen Tonleitern 5 | 3. Skalieren Sie Ressourcen, einschließlich Bilder jeder Tonleiter und Beschreibungen 6 | 7 | Features: 8 | 9 | * Diverse Tonleiter-Auswahlen (Dur, natürliches Moll, harmonisches Moll, melodisches Moll, Dur-Modi, melodische Moll-Modi, Blues-Tonleiter, Pentatonische Tonleitern, Oktatonische Tonleitern, GanztonTonleitern) 10 | * Arpeggio Practic 11 | * Ressourcen zum Erlernen neuer Tonleitern 12 | * Zufällige Auswahl aus benutzerdefinierten Tonleitern< 13 | 14 | Diese App hört Dir nicht beim Üben zu und gibt dir in keiner Weise Feedback. Sie soll Dir nur eine neue Reihenfolge zum Üben Deiner Tonleitern geben. 15 | -------------------------------------------------------------------------------- /fastlane/metadata/android/de/short_description.txt: -------------------------------------------------------------------------------- 1 | Skalen und Arpeggios -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Scale Practice helps musicians practice their scales, by randomizing the order of scales and providing resources to learn about new scales. It contains three different functions: 2 | 3 | 1. Random scale chosen from a list of scale types 4 | 2. Random scale chosen from a list of user inputted scales 5 | 3. Scale resources, including images of each scale and descriptions 6 | 7 | Features: 8 | 9 | * Diverse Scale Selection (Major, Natural Minor, Harmonic Minor, Melodic Minor, Major Modes, Melodic Minor Modes, Blues Scale, Pentatonic Scales, Octatonic Scales, Whole Tone Scales) 10 | * Arpeggio Practic 11 | * Resources to help you learn new scales 12 | * Random selection from user selected scales 13 | 14 | This app does not listen to you practice, or provide you feedback in any way. It is only designed to give you a new order to practice your scales in. 15 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/fastlane/metadata/android/en-US/images/phoneScreenshots/01.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/fastlane/metadata/android/en-US/images/phoneScreenshots/02.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/fastlane/metadata/android/en-US/images/phoneScreenshots/03.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/fastlane/metadata/android/en-US/images/phoneScreenshots/04.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Scales and Arpeggios -------------------------------------------------------------------------------- /fastlane/metadata/ios/copyright.txt: -------------------------------------------------------------------------------- 1 | 2022 Alexander Burdiss 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/apple_tv_privacy_policy.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/description.txt: -------------------------------------------------------------------------------- 1 | This app contains three different functions: 2 | 1. Random scale chosen from a list of scale types 3 | 2. Random scale chosen from a list of user inputted scales 4 | 3. Scale resources, including images of each scale and descriptions 5 | 6 | Features: 7 | -Diverse Scale Selection (Major, Natural Minor, Harmonic Minor, Melodic Minor, Major Modes, Melodic Minor Modes, Blues Scale, Pentatonic Scales, Octatonic Scales, Whole Tone Scales) 8 | -Arpeggio Practic 9 | -Resources to help you learn new scales 10 | -Random selection from user selected scales 11 | 12 | This app does not listen to you practice, or provide you feedback in any way. It is only designed to give you a new order to practice your scales in. 13 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/keywords.txt: -------------------------------------------------------------------------------- 1 | Scale, Practice, music, random, arpeggio, theory, guitar, piano, dice, scales, Solfège 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/marketing_url.txt: -------------------------------------------------------------------------------- 1 | https://alexanderburdiss.com/ 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/name.txt: -------------------------------------------------------------------------------- 1 | Scale Practice - Randomizer 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/privacy_url.txt: -------------------------------------------------------------------------------- 1 | https://alexanderburdiss.com/privacy-policy/ 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/promotional_text.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/release_notes.txt: -------------------------------------------------------------------------------- 1 | Fixes issue where random selections wouldn't randomize types of scales. 2 | Fixes Major Pentatonic Scale image. 3 | Adds Version number to Settings Page for easier App version identification. -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/subtitle.txt: -------------------------------------------------------------------------------- 1 | Scales and Arpeggios 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/en-US/support_url.txt: -------------------------------------------------------------------------------- 1 | https://alexanderburdiss.com/ 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/primary_category.txt: -------------------------------------------------------------------------------- 1 | MUSIC 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/primary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/primary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/demo_password.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/demo_user.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/email_address.txt: -------------------------------------------------------------------------------- 1 | aburdiss@icloud.com 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/first_name.txt: -------------------------------------------------------------------------------- 1 | Alexander 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/last_name.txt: -------------------------------------------------------------------------------- 1 | Burdiss 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/notes.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/review_information/phone_number.txt: -------------------------------------------------------------------------------- 1 | 9286066495 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/screenshots/README.txt: -------------------------------------------------------------------------------- 1 | ## Screenshots Naming Rules 2 | 3 | Put all screenshots you want to use inside the folder of its language (e.g. `en-US`). 4 | The device type will automatically be recognized using the image resolution. 5 | 6 | The screenshots can be named whatever you want, but keep in mind they are sorted 7 | alphabetically, in a human-friendly way. See https://github.com/fastlane/fastlane/pull/18200 for more details. 8 | 9 | ### Exceptions 10 | 11 | #### iPad Pro (3rd Gen) 12.9" 12 | 13 | Since iPad Pro (3rd Gen) 12.9" and iPad Pro (2nd Gen) 12.9" have the same image 14 | resolution, screenshots of the iPad Pro (3rd gen) 12.9" must contain either the 15 | string `iPad Pro (12.9-inch) (3rd generation)`, `IPAD_PRO_3GEN_129`, or `ipadPro129` 16 | (App Store Connect's internal naming of the display family for the 3rd generation iPad Pro) 17 | in its filename to be assigned the correct display family and to be uploaded to 18 | the correct screenshot slot in your app's metadata. 19 | 20 | ### Other Platforms 21 | 22 | #### Apple TV 23 | 24 | Apple TV screenshots should be stored in a subdirectory named `appleTV` with language 25 | folders inside of it. 26 | 27 | #### iMessage 28 | 29 | iMessage screenshots, like the Apple TV ones, should also be stored in a subdirectory 30 | named `iMessage`, with language folders inside of it. 31 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/secondary_category.txt: -------------------------------------------------------------------------------- 1 | EDUCATION 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/secondary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ios/secondary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/ANPLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/ANPLogo.png -------------------------------------------------------------------------------- /img/Amazon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/Amazon.png -------------------------------------------------------------------------------- /img/Apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/Apple.png -------------------------------------------------------------------------------- /img/BrassRoutinesIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/BrassRoutinesIcon.png -------------------------------------------------------------------------------- /img/Fdroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/Fdroid.png -------------------------------------------------------------------------------- /img/Google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/Google.png -------------------------------------------------------------------------------- /img/arpeggios/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/24.png -------------------------------------------------------------------------------- /img/arpeggios/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/25.png -------------------------------------------------------------------------------- /img/arpeggios/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/26.png -------------------------------------------------------------------------------- /img/arpeggios/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/27.png -------------------------------------------------------------------------------- /img/arpeggios/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/28.png -------------------------------------------------------------------------------- /img/arpeggios/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/29.png -------------------------------------------------------------------------------- /img/arpeggios/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/30.png -------------------------------------------------------------------------------- /img/arpeggios/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/31.png -------------------------------------------------------------------------------- /img/arpeggios/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/32.png -------------------------------------------------------------------------------- /img/arpeggios/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/33.png -------------------------------------------------------------------------------- /img/arpeggios/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/arpeggios/34.png -------------------------------------------------------------------------------- /img/scales/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/0.png -------------------------------------------------------------------------------- /img/scales/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/1.png -------------------------------------------------------------------------------- /img/scales/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/10.png -------------------------------------------------------------------------------- /img/scales/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/11.png -------------------------------------------------------------------------------- /img/scales/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/12.png -------------------------------------------------------------------------------- /img/scales/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/13.png -------------------------------------------------------------------------------- /img/scales/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/14.png -------------------------------------------------------------------------------- /img/scales/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/15.png -------------------------------------------------------------------------------- /img/scales/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/16.png -------------------------------------------------------------------------------- /img/scales/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/17.png -------------------------------------------------------------------------------- /img/scales/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/18.png -------------------------------------------------------------------------------- /img/scales/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/19.png -------------------------------------------------------------------------------- /img/scales/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/2.png -------------------------------------------------------------------------------- /img/scales/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/20.png -------------------------------------------------------------------------------- /img/scales/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/21.png -------------------------------------------------------------------------------- /img/scales/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/22.png -------------------------------------------------------------------------------- /img/scales/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/23.png -------------------------------------------------------------------------------- /img/scales/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/3.png -------------------------------------------------------------------------------- /img/scales/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/4.png -------------------------------------------------------------------------------- /img/scales/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/5.png -------------------------------------------------------------------------------- /img/scales/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/6.png -------------------------------------------------------------------------------- /img/scales/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/7.png -------------------------------------------------------------------------------- /img/scales/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/8.png -------------------------------------------------------------------------------- /img/scales/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/img/scales/9.png -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { AppRegistry } from 'react-native'; 6 | import App from './App'; 7 | const appName = 'ScalePractice'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | linkage = ENV['USE_FRAMEWORKS'] 12 | if linkage != nil 13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 14 | use_frameworks! :linkage => linkage.to_sym 15 | end 16 | 17 | target 'ScalePractice' do 18 | config = use_native_modules! 19 | 20 | use_react_native!( 21 | :path => config[:reactNativePath], 22 | # An absolute path to your application root. 23 | :app_path => "#{Pod::Config.instance.installation_root}/.." 24 | ) 25 | 26 | target 'ScalePracticeTests' do 27 | inherit! :complete 28 | # Pods for testing 29 | end 30 | 31 | post_install do |installer| 32 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 33 | react_native_post_install( 34 | installer, 35 | config[:reactNativePath], 36 | :mac_catalyst_enabled => false, 37 | # :ccache_enabled => true 38 | ) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /ios/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategorySystemBootTime 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | 35F9.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategoryUserDefaults 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | CA92.1 29 | 30 | 31 | 32 | NSPrivacyAccessedAPIType 33 | NSPrivacyAccessedAPICategoryDiskSpace 34 | NSPrivacyAccessedAPITypeReasons 35 | 36 | 85F4.1 37 | 38 | 39 | 40 | NSPrivacyCollectedDataTypes 41 | 42 | NSPrivacyTracking 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/ScalePractice.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/ScalePractice.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/ScalePractice/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/ScalePractice/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"ScalePractice"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self bundleURL]; 20 | } 21 | 22 | - (NSURL *)bundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-1024.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x-1.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x-1.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x-1.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-76.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-76@2x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePractice/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png -------------------------------------------------------------------------------- /ios/ScalePractice/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/ScalePractice/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | 28 | NSAllowsArbitraryLoads 29 | 30 | NSAllowsLocalNetworking 31 | 32 | 33 | NSLocationWhenInUseUsageDescription 34 | 35 | UIAppFonts 36 | 37 | AntDesign.ttf 38 | Entypo.ttf 39 | EvilIcons.ttf 40 | Feather.ttf 41 | FontAwesome.ttf 42 | FontAwesome5_Brands.ttf 43 | FontAwesome5_Regular.ttf 44 | FontAwesome5_Solid.ttf 45 | Fontisto.ttf 46 | Foundation.ttf 47 | Ionicons.ttf 48 | MaterialCommunityIcons.ttf 49 | MaterialIcons.ttf 50 | Octicons.ttf 51 | SimpleLineIcons.ttf 52 | Zocial.ttf 53 | 54 | UILaunchStoryboardName 55 | LaunchScreen 56 | UIRequiredDeviceCapabilities 57 | 58 | arm64 59 | 60 | UIRequiresFullScreen 61 | 62 | UISupportedInterfaceOrientations 63 | 64 | UIInterfaceOrientationPortrait 65 | 66 | UIViewControllerBasedStatusBarAppearance 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /ios/ScalePractice/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ios/ScalePracticeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/ScalePracticeTests/ScalePracticeTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ScalePracticeTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ScalePracticeTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "output-onlinepngtools-4.png", 5 | "idiom": "watch", 6 | "scale": "2x", 7 | "screen-width": "<=145" 8 | }, 9 | { 10 | "filename": "output-onlinepngtools-5.png", 11 | "idiom": "watch", 12 | "scale": "2x", 13 | "screen-width": ">161" 14 | }, 15 | { 16 | "filename": "output-onlinepngtools-6.png", 17 | "idiom": "watch", 18 | "scale": "2x", 19 | "screen-width": ">145" 20 | }, 21 | { 22 | "filename": "output-onlinepngtools-7.png", 23 | "idiom": "watch", 24 | "scale": "2x", 25 | "screen-width": ">183" 26 | } 27 | ], 28 | "info": { 29 | "author": "xcode", 30 | "version": 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-4.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-5.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-6.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/output-onlinepngtools-7.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets": [ 3 | { 4 | "filename": "Circular.imageset", 5 | "idiom": "watch", 6 | "role": "circular" 7 | }, 8 | { 9 | "filename": "Extra Large.imageset", 10 | "idiom": "watch", 11 | "role": "extra-large" 12 | }, 13 | { 14 | "filename": "Graphic Bezel.imageset", 15 | "idiom": "watch", 16 | "role": "graphic-bezel" 17 | }, 18 | { 19 | "filename": "Graphic Circular.imageset", 20 | "idiom": "watch", 21 | "role": "graphic-circular" 22 | }, 23 | { 24 | "filename": "Graphic Corner.imageset", 25 | "idiom": "watch", 26 | "role": "graphic-corner" 27 | }, 28 | { 29 | "filename": "Graphic Extra Large.imageset", 30 | "idiom": "watch", 31 | "role": "graphic-extra-large" 32 | }, 33 | { 34 | "filename": "Graphic Large Rectangular.imageset", 35 | "idiom": "watch", 36 | "role": "graphic-large-rectangular" 37 | }, 38 | { 39 | "filename": "Modular.imageset", 40 | "idiom": "watch", 41 | "role": "modular" 42 | }, 43 | { 44 | "filename": "Utilitarian.imageset", 45 | "idiom": "watch", 46 | "role": "utilitarian" 47 | } 48 | ], 49 | "info": { 50 | "author": "xcode", 51 | "version": 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "output-onlinepngtools.png", 5 | "idiom": "watch", 6 | "scale": "2x", 7 | "screen-width": "<=145" 8 | }, 9 | { 10 | "filename": "output-onlinepngtools-3.png", 11 | "idiom": "watch", 12 | "scale": "2x", 13 | "screen-width": ">161" 14 | }, 15 | { 16 | "filename": "output-onlinepngtools-2.png", 17 | "idiom": "watch", 18 | "scale": "2x", 19 | "screen-width": ">145" 20 | }, 21 | { 22 | "filename": "output-onlinepngtools-4.png", 23 | "idiom": "watch", 24 | "scale": "2x", 25 | "screen-width": ">183" 26 | } 27 | ], 28 | "info": { 29 | "author": "xcode", 30 | "version": 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools-2.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools-3.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools-4.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/output-onlinepngtools.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "watch", 5 | "scale": "2x", 6 | "screen-width": "<=145" 7 | }, 8 | { 9 | "filename": "output-onlinepngtools-7.png", 10 | "idiom": "watch", 11 | "scale": "2x", 12 | "screen-width": ">161" 13 | }, 14 | { 15 | "idiom": "watch", 16 | "scale": "2x", 17 | "screen-width": ">145" 18 | }, 19 | { 20 | "filename": "output-onlinepngtools-8.png", 21 | "idiom": "watch", 22 | "scale": "2x", 23 | "screen-width": ">183" 24 | } 25 | ], 26 | "info": { 27 | "author": "xcode", 28 | "version": 1 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/output-onlinepngtools-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/output-onlinepngtools-7.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/output-onlinepngtools-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/output-onlinepngtools-8.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "watch", 5 | "scale": "2x", 6 | "screen-width": "<=145" 7 | }, 8 | { 9 | "filename": "output-onlinepngtools-7.png", 10 | "idiom": "watch", 11 | "scale": "2x", 12 | "screen-width": ">161" 13 | }, 14 | { 15 | "idiom": "watch", 16 | "scale": "2x", 17 | "screen-width": ">145" 18 | }, 19 | { 20 | "filename": "output-onlinepngtools-8.png", 21 | "idiom": "watch", 22 | "scale": "2x", 23 | "screen-width": ">183" 24 | } 25 | ], 26 | "info": { 27 | "author": "xcode", 28 | "version": 1 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/output-onlinepngtools-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/output-onlinepngtools-7.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/output-onlinepngtools-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/output-onlinepngtools-8.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "watch", 5 | "scale": "2x", 6 | "screen-width": "<=145" 7 | }, 8 | { 9 | "filename": "output-onlinepngtools.png", 10 | "idiom": "watch", 11 | "scale": "2x", 12 | "screen-width": ">161" 13 | }, 14 | { 15 | "idiom": "watch", 16 | "scale": "2x", 17 | "screen-width": ">145" 18 | }, 19 | { 20 | "filename": "output-onlinepngtools-2.png", 21 | "idiom": "watch", 22 | "scale": "2x", 23 | "screen-width": ">183" 24 | } 25 | ], 26 | "info": { 27 | "author": "xcode", 28 | "version": 1 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/output-onlinepngtools-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/output-onlinepngtools-2.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/output-onlinepngtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/output-onlinepngtools.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "watch", 5 | "scale": "2x", 6 | "screen-width": "<=145" 7 | }, 8 | { 9 | "idiom": "watch", 10 | "scale": "2x", 11 | "screen-width": ">161" 12 | }, 13 | { 14 | "idiom": "watch", 15 | "scale": "2x", 16 | "screen-width": ">145" 17 | }, 18 | { 19 | "idiom": "watch", 20 | "scale": "2x", 21 | "screen-width": ">183" 22 | } 23 | ], 24 | "info": { 25 | "author": "xcode", 26 | "version": 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "watch", 5 | "scale": "2x", 6 | "screen-width": ">161" 7 | }, 8 | { 9 | "idiom": "watch", 10 | "scale": "2x", 11 | "screen-width": ">183" 12 | } 13 | ], 14 | "info": { 15 | "author": "xcode", 16 | "version": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "output-onlinepngtools-4.png", 5 | "idiom": "watch", 6 | "scale": "2x", 7 | "screen-width": "<=145" 8 | }, 9 | { 10 | "filename": "output-onlinepngtools-7.png", 11 | "idiom": "watch", 12 | "scale": "2x", 13 | "screen-width": ">161" 14 | }, 15 | { 16 | "filename": "output-onlinepngtools-5.png", 17 | "idiom": "watch", 18 | "scale": "2x", 19 | "screen-width": ">145" 20 | }, 21 | { 22 | "filename": "output-onlinepngtools-6.png", 23 | "idiom": "watch", 24 | "scale": "2x", 25 | "screen-width": ">183" 26 | } 27 | ], 28 | "info": { 29 | "author": "xcode", 30 | "version": 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-4.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-5.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-6.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/output-onlinepngtools-7.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "output-onlinepngtools.png", 5 | "idiom": "watch", 6 | "scale": "2x", 7 | "screen-width": "<=145" 8 | }, 9 | { 10 | "filename": "output-onlinepngtools-2.png", 11 | "idiom": "watch", 12 | "scale": "2x", 13 | "screen-width": ">161" 14 | }, 15 | { 16 | "filename": "output-onlinepngtools-3.png", 17 | "idiom": "watch", 18 | "scale": "2x", 19 | "screen-width": ">145" 20 | }, 21 | { 22 | "filename": "output-onlinepngtools-4.png", 23 | "idiom": "watch", 24 | "scale": "2x", 25 | "screen-width": ">183" 26 | } 27 | ], 28 | "info": { 29 | "author": "xcode", 30 | "version": 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools-2.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools-3.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools-4.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/output-onlinepngtools.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "author": "xcode", 4 | "version": 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // ScalePracticeWatch WatchKit Extension 4 | // 5 | // Created by Alexander Burdiss on 11/25/20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @State var currentIndex = 0 12 | @State var alertIsShowing = false 13 | @State var letterNames = ["C", "C♯", "D", "E♭", "E", "F", "F♯", "G", "A♭", "A", "B♭", "B"] 14 | 15 | var body: some View { 16 | VStack { 17 | Spacer() 18 | HStack { 19 | Spacer() 20 | Text(letterNames[currentIndex]) 21 | .font(.largeTitle) 22 | Spacer() 23 | } 24 | Spacer() 25 | } 26 | .padding(.bottom) 27 | 28 | .background( 29 | LinearGradient( 30 | gradient: Gradient( 31 | colors: [ 32 | Color(red: 0.4627450980392157, green: 0.22745098039215686, blue: 0.9647058823529412), 33 | Color(red: 0.807843137254902, green: 0.29411764705882354, blue: 0.9647058823529412) 34 | ] 35 | ), 36 | startPoint: .top, 37 | endPoint: .bottom 38 | ) 39 | ) 40 | .cornerRadius(20) 41 | 42 | Button(action: { 43 | advanceCounter() 44 | }) { 45 | Text("Randomize") 46 | } 47 | .accessibility(value: Text("\(letterNames[currentIndex])") 48 | ) 49 | .onAppear { 50 | letterNames.shuffle() 51 | currentIndex = 0 52 | } 53 | .alert(isPresented: $alertIsShowing) { 54 | Alert( 55 | title: Text("All Scales Practiced"), 56 | dismissButton: .default(Text("Keep Practicing")){ 57 | currentIndex = 0 58 | letterNames.shuffle() 59 | } 60 | ) 61 | } 62 | } 63 | 64 | func advanceCounter () { 65 | if ( currentIndex == 11 ) { 66 | alertIsShowing = true 67 | } else { 68 | currentIndex += 1 69 | } 70 | } 71 | 72 | } 73 | 74 | struct ContentView_Previews: PreviewProvider { 75 | static var previews: some View { 76 | ContentView() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/HostingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostingController.swift 3 | // ScalePracticeWatch Extension 4 | // 5 | // Created by Alexander Burdiss on 12/1/20. 6 | // 7 | 8 | import WatchKit 9 | import Foundation 10 | import SwiftUI 11 | 12 | class HostingController: WKHostingController { 13 | override var body: ContentView { 14 | return ContentView() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ScalePracticeWatch Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | CLKComplicationPrincipalClass 24 | $(PRODUCT_MODULE_NAME).ComplicationController 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | com.AlexanderBurdiss.ScalePractice.watchkitapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | WKExtensionDelegateClassName 36 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 37 | WKRunsIndependentlyOfCompanionApp 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "author": "xcode", 4 | "version": 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch Extension/PushNotificationPayload.apns: -------------------------------------------------------------------------------- 1 | { 2 | "aps": { 3 | "alert": { 4 | "body": "Test message", 5 | "title": "Optional title", 6 | "subtitle": "Optional subtitle" 7 | }, 8 | "category": "myCategory", 9 | "thread-id": "5280" 10 | }, 11 | 12 | "WatchKit Simulator Actions": [ 13 | { 14 | "title": "First Button", 15 | "identifier": "firstButtonAction" 16 | } 17 | ], 18 | 19 | "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App." 20 | } 21 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "srgb", 6 | "components": { 7 | "alpha": "1.000", 8 | "blue": "1.000", 9 | "green": "0.216", 10 | "red": "0.750" 11 | } 12 | }, 13 | "idiom": "universal" 14 | } 15 | ], 16 | "info": { 17 | "author": "xcode", 18 | "version": 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-1024@1x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-108@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-108@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-24@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-24@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-24@3x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-27-5@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-27-5@2x-1.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-29@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-40@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-44@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-44@2x-1.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-50@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-86@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aburdiss/ScalePractice/c3709a042aabfdaabb96d59a07c57fd5f314ad82/ios/ScalePracticeWatch/Assets.xcassets/AppIcon.appiconset/AppIcon-1024-98@2x.png -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "author": "xcode", 4 | "version": 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ios/ScalePracticeWatch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ScalePractice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | UIInterfaceOrientationPortraitUpsideDown 27 | 28 | WKCompanionAppBundleIdentifier 29 | com.AlexanderBurdiss.ScalePractice 30 | WKWatchKitApp 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /ios/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 11/15/20. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "Scale Practice"; 10 | "CFBundleName" = "Scale Practice"; 11 | -------------------------------------------------------------------------------- /ios/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 12/1/20. 6 | 7 | */ 8 | 9 | // Watch App Translations 10 | "Scale Practice" = "Scale Practice"; 11 | "Randomize" = "Randomize"; 12 | "All Scales Practiced" = "All Scales Practiced"; 13 | "Keep Practicing" = "Keep Practicing"; 14 | -------------------------------------------------------------------------------- /ios/es-419.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 11/15/20. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "Escalas"; 10 | "CFBundleName" = "Práctica de Escalas"; 11 | -------------------------------------------------------------------------------- /ios/es-419.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 12/1/20. 6 | 7 | */ 8 | 9 | // Watch App Translations 10 | "Scale Practice" = "Escala Aleatoria"; 11 | "Randomize" = "Aleatorizar"; 12 | "All Scales Practiced" = "Todas las escalas practicadas"; 13 | "Keep Practicing" = "Sigue Practicando"; 14 | -------------------------------------------------------------------------------- /ios/fr.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 11/15/20. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "Les gammes"; 10 | "CFBundleName" = "Pratiquer les gammes"; 11 | -------------------------------------------------------------------------------- /ios/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 12/1/20. 6 | 7 | */ 8 | 9 | // Watch App Translations 10 | "Scale Practice" = "Pratique en gammes"; 11 | "Randomize" = "Randomiser"; 12 | "All Scales Practiced" = "Toutes les gammes pratiquées"; 13 | "Keep Practicing" = "Continuez à pratiquer"; 14 | -------------------------------------------------------------------------------- /ios/ja.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 11/15/20. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "音階練習"; 10 | "CFBundleName" = "音階練習"; 11 | -------------------------------------------------------------------------------- /ios/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 12/1/20. 6 | 7 | */ 8 | 9 | // Watch App Translations 10 | "Scale Practice" = "音階練習"; 11 | "Randomize" = "ランダム化"; 12 | "All Scales Practiced" = "すべての音階が練習されました"; 13 | "Keep Practicing" = "練習を続けます"; 14 | -------------------------------------------------------------------------------- /ios/ko.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 11/15/20. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "음력 연습"; 10 | "CFBundleName" = "음력 연습"; 11 | -------------------------------------------------------------------------------- /ios/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 12/1/20. 6 | 7 | */ 8 | 9 | // Watch App Translations 10 | "Scale Practice" = "음력 연습"; 11 | "Randomize" = "무작위 화"; 12 | "All Scales Practiced" = "모든 음계가 연습되었습니다"; 13 | "Keep Practicing" = "연습을 계속합니다"; 14 | -------------------------------------------------------------------------------- /ios/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 11/15/20. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "音阶练习"; 10 | "CFBundleName" = "音阶练习"; 11 | -------------------------------------------------------------------------------- /ios/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | ScalePractice 4 | 5 | Created by Alexander Burdiss on 12/1/20. 6 | 7 | */ 8 | 9 | // Watch App Translations 10 | "Scale Practice" = "音阶练习"; 11 | "Randomize" = "开始"; 12 | "All Scales Practiced" = "每个音阶练习了"; 13 | "Keep Practicing" = "保持练习"; 14 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | // jest.config.ts 2 | import { createJsWithBabelPreset, JestConfigWithTsJest } from 'ts-jest'; 3 | 4 | const jsWithBabelPreset = createJsWithBabelPreset({ 5 | tsconfig: 'tsconfig.spec.json', 6 | babelConfig: true, 7 | }); 8 | 9 | const jestConfig: JestConfigWithTsJest = { 10 | preset: 'react-native', 11 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 12 | roots: ['/src'], 13 | setupFiles: [ 14 | '/jest/setup.js', 15 | '/node_modules/react-native-gesture-handler/jestSetup.js', 16 | ], 17 | moduleNameMapper: { 18 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 19 | 'identity-obj-proxy', 20 | }, 21 | transform: jsWithBabelPreset.transform, 22 | transformIgnorePatterns: [ 23 | '/node_modules/(?!(@react-native|react-native|react-native-popover-view|react-native-linear-gradient|react-native-animatable|react-native-scalable-image|react-native-modal|react-native-iphone-x-helper|react-native-reanimated|react-native-vector-icons|react-native-screens|react-native-splash-screen|react-navigation-tabs|@?react-navigation|react-native-gesture-handler|@react-native-community/segmented-control|react-native-picker-select|@react-native-picker/picker)/)', 24 | ], 25 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 26 | }; 27 | 28 | export default jestConfig; 29 | -------------------------------------------------------------------------------- /jest/MockContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SafeAreaProvider } from 'react-native-safe-area-context'; 3 | import { PreferencesContext } from '../src/Model/Preferences'; 4 | 5 | const MockContext = ({ children }) => { 6 | let state = { 7 | repeat: true, 8 | }; 9 | let dispatch = jest.fn(); 10 | 11 | return ( 12 | 13 | 14 | {children} 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default MockContext; 21 | -------------------------------------------------------------------------------- /jest/setup.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler/jestSetup'; 2 | import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'; 3 | import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'; 4 | import { NativeModules as RNNativeModules } from 'react-native'; 5 | 6 | jest.mock('react-native-reanimated', () => { 7 | const Reanimated = require('react-native-reanimated/mock'); 8 | 9 | // The mock for `call` immediately calls the callback which is incorrect 10 | // So we override it with a no-op 11 | Reanimated.default.call = () => {}; 12 | 13 | return Reanimated; 14 | }); 15 | 16 | jest.mock('react-native-idle-timer', () => ({ 17 | setIdleTimerDisabled: () => {}, 18 | })); 19 | 20 | jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage); 21 | jest.mock('react-native-device-info', () => mockRNDeviceInfo); 22 | jest.mock('react-native-localize', () => { 23 | return { 24 | getLocales: jest.fn(), 25 | findBestAvailableLanguage: jest.fn(() => ({ 26 | languageTag: 'en', 27 | isRTL: false, 28 | })), 29 | addEventListener: jest.fn(), 30 | addEventListener: jest.fn(), 31 | removeEventListener: jest.fn(), 32 | // you can add other functions mock here that you are using 33 | }; 34 | }); 35 | 36 | RNNativeModules.UIManager = RNNativeModules.UIManager || {}; 37 | RNNativeModules.UIManager.RCTView = RNNativeModules.UIManager.RCTView || {}; 38 | RNNativeModules.RNGestureHandlerModule = 39 | RNNativeModules.RNGestureHandlerModule || { 40 | State: { BEGAN: 'BEGAN', FAILED: 'FAILED', ACTIVE: 'ACTIVE', END: 'END' }, 41 | attachGestureHandler: jest.fn(), 42 | createGestureHandler: jest.fn(), 43 | dropGestureHandler: jest.fn(), 44 | updateGestureHandler: jest.fn(), 45 | }; 46 | RNNativeModules.PlatformConstants = RNNativeModules.PlatformConstants || { 47 | forceTouchAvailable: false, 48 | }; 49 | 50 | // Leave this at the bottom to run after all the imports. 51 | jest.useFakeTimers(); 52 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | 3 | /** 4 | * Metro configuration 5 | * https://reactnative.dev/docs/metro 6 | * 7 | * @type {import('metro-config').MetroConfig} 8 | */ 9 | const config = {}; 10 | 11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 12 | -------------------------------------------------------------------------------- /scripts/generateComponent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # generateComponent.sh 3 | # Author: Alexander Burdiss 4 | # Since: 9/7/21 5 | # Version: 1.0.0 6 | # Description: Generates a React component and all the necessary files that go 7 | # along with it. 8 | 9 | path=$1 10 | component=$2 11 | date=`date +"%D"` 12 | 13 | if [[ ! -d "src/$path" ]]; then 14 | echo 15 | echo "Directory src/$path Doesn't exist!" 16 | echo 17 | exit 1 18 | elif [[ -d "src/$path/$component" ]]; then 19 | echo 20 | echo "Component src/$path/$component Already exists!" 21 | echo 22 | exit 1 23 | fi 24 | 25 | mkdir "src/$path/$component" 26 | 27 | # Make JS File 28 | echo "import React from 'react'; 29 | import { StyleSheet, View, Text } from 'react-native'; 30 | 31 | /** 32 | * @namespace $component 33 | * @function $component 34 | * @author Alexander Burdiss 35 | * @since $date 36 | * @version 1.0.0 37 | */ 38 | export default function $component() { 39 | return ( 40 | 41 | $component Works! 42 | 43 | ); 44 | } 45 | 46 | const styles = StyleSheet.create({ 47 | container: {}, 48 | });" > "src/$path/$component/$component.js" 49 | 50 | # Make Jest test file 51 | echo "import 'react-native'; 52 | import React from 'react'; 53 | import { render } from '@testing-library/react-native'; 54 | 55 | import $component from './$component'; 56 | 57 | describe('renders $component', () => { 58 | test('renders base component', () => { 59 | render(<$component />); 60 | }); 61 | });" > "src/$path/$component/$component.test.js" 62 | 63 | echo 64 | echo "Component src/$path/$component Created Successfuly!" 65 | echo 66 | exit 0 67 | -------------------------------------------------------------------------------- /scripts/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # pre-commit hook for ScalePractice 4 | # Created by Alexander Burdiss on 10/25/22 5 | # Version 1.0.0 6 | # 7 | 8 | set -e 9 | 10 | npm run prettier 11 | 12 | npm run lint 13 | 14 | npm run test 15 | -------------------------------------------------------------------------------- /scripts/reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # reset.sh 3 | # Author: Alexander Burdiss 4 | # Since: 2/4/22 5 | # Version: 1.0.0 6 | # Description: Does a basic reset of a React Native project 7 | 8 | watchman watch-del-all 9 | npm start -- --reset-cache 10 | -------------------------------------------------------------------------------- /src/Components/AddToListButton/AddToListButton.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import AddToListButton from './AddToListButton'; 4 | 5 | import { fireEvent, render } from '@testing-library/react-native'; 6 | 7 | test('AddToListButton renders correctly', () => { 8 | const { queryByText } = render(); 9 | expect(queryByText(/Add/)).toBeTruthy(); 10 | }); 11 | 12 | test('AddToListButton handles click correctly', () => { 13 | const buttonHandler = jest.fn(); 14 | const { getByText } = render(); 15 | expect(buttonHandler).not.toHaveBeenCalled(); 16 | fireEvent.press(getByText(/Add/)); 17 | expect(buttonHandler).toHaveBeenCalledTimes(1); 18 | fireEvent.press(getByText(/Add/)); 19 | expect(buttonHandler).toHaveBeenCalledTimes(2); 20 | }); 21 | -------------------------------------------------------------------------------- /src/Components/AddToListButton/AddToListButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | View, 5 | Pressable, 6 | Text, 7 | StyleSheet, 8 | GestureResponderEvent, 9 | } from 'react-native'; 10 | 11 | import { useDarkMode } from '../../utils'; 12 | import { colors } from '../../Model/Model'; 13 | import { translate } from '../../Translations/TranslationModel'; 14 | 15 | /** 16 | * @function AddToListButton 17 | * @component 18 | * @description A green button that says "Add To List", and calls whatever 19 | * function is passed in. 20 | * Created 12/7/20 21 | * @param {Object} props JSX props passed to this React component 22 | * @param {Function} props.handler The function to call when this button is 23 | * pressed 24 | * @returns {JSX.Element} JSX Render instructions 25 | * 26 | * @copyright 2023 Alexander Burdiss 27 | * @author Alexander Burdiss 28 | * @since 9/23/23 29 | * @version 1.0.2 30 | * @example 31 | * 32 | */ 33 | export default function AddToListButton({ 34 | handler, 35 | }: { 36 | handler: (event: GestureResponderEvent) => void; 37 | }) { 38 | const DARKMODE = useDarkMode(); 39 | const styles = StyleSheet.create({ 40 | text: { 41 | textAlign: 'center', 42 | color: DARKMODE ? colors.greenDark : colors.greenLight, 43 | }, 44 | }); 45 | 46 | return ( 47 | 48 | ({ 55 | borderRadius: 8, 56 | borderColor: DARKMODE ? colors.greenDark : colors.greenLight, 57 | opacity: pressed ? 0.7 : 1, 58 | borderWidth: 1, 59 | margin: 10, 60 | padding: 12, 61 | overflow: 'hidden', 62 | })} 63 | onPress={handler} 64 | > 65 | {translate('Add To List')} 66 | 67 | 68 | ); 69 | } 70 | 71 | AddToListButton.propTypes = { 72 | handler: PropTypes.func, 73 | }; 74 | -------------------------------------------------------------------------------- /src/Components/AddToListButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './AddToListButton'; 2 | -------------------------------------------------------------------------------- /src/Components/AllScalesButton/AllScalesButton.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import AllScalesButton from './AllScalesButton'; 4 | 5 | import { fireEvent, render } from '@testing-library/react-native'; 6 | 7 | test('AllScalesButton renders correctly', () => { 8 | const { queryByText } = render( 9 | Hello, World!, 10 | ); 11 | expect(queryByText(/Hello, World!/)).toBeTruthy(); 12 | }); 13 | 14 | test('AllScalesButton calls function correctly', () => { 15 | const buttonHandler = jest.fn(); 16 | const { getByText } = render( 17 | Press Me, 18 | ); 19 | expect(buttonHandler).not.toHaveBeenCalled(); 20 | fireEvent.press(getByText(/Press Me/)); 21 | expect(buttonHandler).toHaveBeenCalledTimes(1); 22 | fireEvent.press(getByText(/Press Me/)); 23 | expect(buttonHandler).toHaveBeenCalledTimes(2); 24 | }); 25 | -------------------------------------------------------------------------------- /src/Components/AllScalesButton/AllScalesButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | Pressable, 5 | StyleSheet, 6 | Text, 7 | GestureResponderEvent, 8 | } from 'react-native'; 9 | import { useDarkMode } from '../../utils'; 10 | 11 | import { colors } from '../../Model/Model'; 12 | 13 | /** 14 | * @function AllScalesButton 15 | * @component 16 | * @description A gray button that is meant to trigger all switches on a page. 17 | * Basic styles are already applied. 18 | * Created 10/12/20 19 | * @param {Object} props The JSX props passed to this React component 20 | * @param {string} props.children The text to render in the button 21 | * @param {Function} props.handler The function to call when the button is 22 | * pressed 23 | * @returns {JSX.Element} JSX render instructions 24 | * 25 | * @copyright 2023 Alexander Burdiss 26 | * @author Alexander Burdiss 27 | * @since 9/23/23 28 | * @version 1.0.1 29 | * 30 | * @example 31 | * 32 | * Hello, World! 33 | * 34 | */ 35 | export default function AllScalesButton({ 36 | children, 37 | handler, 38 | accessibilityHint, 39 | }: { 40 | children: React.ReactNode; 41 | handler: (event: GestureResponderEvent) => void; 42 | accessibilityHint?: string; 43 | }) { 44 | const DARKMODE = useDarkMode(); 45 | const styles = StyleSheet.create({ 46 | text: { 47 | textAlign: 'center', 48 | color: DARKMODE ? colors.systemGray : colors.systemGray, 49 | fontSize: 16, 50 | }, 51 | }); 52 | 53 | return ( 54 | ({ 62 | borderRadius: 8, 63 | borderColor: DARKMODE ? colors.systemGray : colors.systemGray, 64 | borderWidth: 1, 65 | marginVertical: 10, 66 | padding: 10, 67 | opacity: pressed ? 0.7 : 1, 68 | overflow: 'hidden', 69 | })} 70 | > 71 | {children} 72 | 73 | ); 74 | } 75 | 76 | AllScalesButton.propTypes = { 77 | handler: PropTypes.func, 78 | children: PropTypes.node, 79 | }; 80 | -------------------------------------------------------------------------------- /src/Components/AllScalesButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './AllScalesButton'; 2 | -------------------------------------------------------------------------------- /src/Components/HeaderButton/HeaderButton.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import HeaderButton from './HeaderButton'; 4 | 5 | import { fireEvent, render } from '@testing-library/react-native'; 6 | 7 | test('HeaderButton renders correctly', () => { 8 | const { queryByText } = render( 9 | Scales, 10 | ); 11 | expect(queryByText(/Scales/)).toBeTruthy(); 12 | }); 13 | 14 | test('HeaderButton function calls correctly on press', () => { 15 | const buttonHandler = jest.fn(); 16 | const { getByText } = render( 17 | Arpeggios, 18 | ); 19 | expect(buttonHandler).not.toHaveBeenCalled(); 20 | fireEvent.press(getByText(/Arpeggios/)); 21 | expect(buttonHandler).toHaveBeenCalledTimes(1); 22 | fireEvent.press(getByText(/Arpeggios/)); 23 | expect(buttonHandler).toHaveBeenCalledTimes(2); 24 | }); 25 | -------------------------------------------------------------------------------- /src/Components/HeaderButton/HeaderButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | Text, 5 | Pressable, 6 | StyleSheet, 7 | GestureResponderEvent, 8 | } from 'react-native'; 9 | import { useDarkMode } from '../../utils'; 10 | 11 | import { colors } from '../../Model/Model'; 12 | import { translate } from '../../Translations/TranslationModel'; 13 | 14 | /** 15 | * @function HeaderButton 16 | * @description A simple button to live on the header and provide additional 17 | * navigation options in the app. 18 | * Created 10/11/20 19 | * @param {Object} props JSX props passed to this React component 20 | * @param {string} props.children The text to render inside this button. 21 | * @param {Function} props.handler The function to call when this button is 22 | * pressed. 23 | * @returns {JSX.Element} JSX render instructions 24 | * 25 | * @copyright 2023 Alexander Burdiss 26 | * @author Alexander Burdiss 27 | * @since 9/23/23 28 | * @version 1.1.2 29 | * 30 | * @example 31 | * 32 | * Hello, World! 33 | * 34 | */ 35 | export default function HeaderButton({ 36 | children, 37 | handler, 38 | }: { 39 | children: string; 40 | handler: (event: GestureResponderEvent) => void; 41 | }) { 42 | const DARKMODE = useDarkMode(); 43 | const styles = StyleSheet.create({ 44 | text: { 45 | color: DARKMODE ? colors.purpleDark : colors.purpleLight, 46 | fontSize: 16, 47 | }, 48 | }); 49 | 50 | return ( 51 | ({ 61 | padding: 8, 62 | marginRight: 4, 63 | opacity: pressed ? 0.7 : 1, 64 | })} 65 | > 66 | 67 | {children} 68 | 69 | 70 | ); 71 | } 72 | 73 | HeaderButton.propTypes = { 74 | children: PropTypes.node, 75 | handler: PropTypes.func, 76 | }; 77 | -------------------------------------------------------------------------------- /src/Components/HeaderButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './HeaderButton'; 2 | -------------------------------------------------------------------------------- /src/Components/LargeScaleDisplay/LargeScaleDisplay.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import LargeScaleDisplay from './LargeScaleDisplay'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('LargeScaleDisplay renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/LargeScaleDisplay/LargeScaleDisplay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, Text } from 'react-native'; 4 | import LinearGradient from 'react-native-linear-gradient'; 5 | import { useDarkMode } from '../../utils'; 6 | 7 | import { colors } from '../../Model/Model'; 8 | 9 | /** 10 | * @function LargeScaleDisplay 11 | * @component 12 | * @description A styled text box that shows the currently selected scale 13 | * Created 6/11/21 14 | * @param {Object} props JSX props passed to this React component 15 | * @param {string} props.children The text to render inside this component 16 | * @returns {JSX.Element} JSX render instructions 17 | * 18 | * @copyright 2023 Alexander Burdiss 19 | * @author Alexander Burdiss 20 | * @since 9/23/23 21 | * @version 1.0.1 22 | * 23 | * @example 24 | * Hello, World! 25 | */ 26 | export default function LargeScaleDisplay({ children }: { children?: string }) { 27 | const DARKMODE = useDarkMode(); 28 | 29 | return ( 30 | 33 | 45 | 58 | {children} 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | LargeScaleDisplay.propTypes = { 66 | children: PropTypes.node, 67 | }; 68 | -------------------------------------------------------------------------------- /src/Components/LargeScaleDisplay/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LargeScaleDisplay'; 2 | -------------------------------------------------------------------------------- /src/Components/ListItems/FlatListItem/FlatListItem.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import FlatListItem from './FlatListItem'; 4 | import MockContext from '../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('FlatListItem renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/ListItems/FlatListItem/FlatListItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, Text, Pressable, StyleSheet } from 'react-native'; 4 | import { useNavigation } from '@react-navigation/native'; 5 | import { useDarkMode } from '../../../utils'; 6 | 7 | import { colors } from '../../../Model/Model'; 8 | import { translate } from '../../../Translations/TranslationModel'; 9 | 10 | /** 11 | * @function FlatListItem 12 | * @component 13 | * @description Used as a render item in the ScaleDescription scroll view. 14 | * Created: 11/15/23 15 | * @param {Object} props JSX props passed to this React component 16 | * @param {Object} props.data The data to display in this list item. 17 | * @return {JSX.Element} JSX render instructions 18 | * 19 | * @copyright 2023 Alexander Burdiss 20 | * @author Alexander Burdiss 21 | * @since 11/15/20 22 | * @version 1.0.2 23 | * 24 | * @example 25 | * 26 | */ 27 | export default function FlatListItem({ data }: { data?: { name: string } }) { 28 | const navigation = useNavigation<{ navigate: Function }>(); 29 | const DARKMODE = useDarkMode(); 30 | const styles = StyleSheet.create({ 31 | container: { 32 | borderBottomColor: DARKMODE 33 | ? colors.systemGray5Dark 34 | : colors.systemGray5Light, 35 | borderBottomWidth: 1, 36 | paddingVertical: 15, 37 | }, 38 | }); 39 | 40 | return ( 41 | { 49 | navigation.navigate('Scale Detail', data); 50 | }} 51 | style={({ pressed }) => ({ 52 | paddingLeft: 20, 53 | backgroundColor: DARKMODE ? colors.systemGray6Dark : colors.white, 54 | opacity: pressed ? 0.7 : 1, 55 | })} 56 | > 57 | 58 | 63 | {translate(data?.name)} 64 | 65 | 66 | 67 | ); 68 | } 69 | 70 | FlatListItem.propTypes = { 71 | data: PropTypes.object, 72 | }; 73 | -------------------------------------------------------------------------------- /src/Components/ListItems/FlatListItem/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './FlatListItem'; 2 | -------------------------------------------------------------------------------- /src/Components/ListItems/InternalListItem/InternalListItem.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import InternalListItem from './InternalListItem'; 4 | import MockContext from '../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('InternalListItem renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/ListItems/InternalListItem/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './InternalListItem'; 2 | -------------------------------------------------------------------------------- /src/Components/ListItems/LinkListItem/LinkListItem.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import LinkListItem from './LinkListItem'; 4 | import MockContext from '../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('LinkListItem renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/ListItems/LinkListItem/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LinkListItem'; 2 | -------------------------------------------------------------------------------- /src/Components/ListItems/SwitchListItem/SwitchListItem.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import SwitchListItem from './SwitchListItem'; 4 | import MockContext from '../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('SwitchListItem renders correctly', () => { 9 | render( 10 | 11 | 16 | , 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Components/ListItems/SwitchListItem/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SwitchListItem'; 2 | -------------------------------------------------------------------------------- /src/Components/ListItems/TextListItem/TextListItem.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import TextListItem from './TextListItem'; 4 | import MockContext from '../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('TextListItem renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/ListItems/TextListItem/TextListItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, Text, StyleSheet } from 'react-native'; 4 | 5 | import { colors } from '../../../Model/Model'; 6 | import { translate as translateFunc } from '../../../Translations/TranslationModel'; 7 | import { useDarkMode } from '../../../utils'; 8 | 9 | /** 10 | * @function TextListItem 11 | * @component 12 | * @description A rendered Text list item. This is currently only being 13 | * used to display copyright information, so it is not being translated. 14 | * Created 11/15/20 15 | * @param {Object} props The JSX props passed to this React component 16 | * @param {Object} props.item The item to be rendered. 17 | * @param {boolean} props.translate Whether or not this item should be 18 | * translated 19 | * @returns {JSX.Element} JSX render instructions 20 | * 21 | * @copyright 2023 Alexander Burdiss 22 | * @author Alexander Burdiss 23 | * @since 9/23/23 24 | * @version 1.1.1 25 | * 26 | * @example 27 | * 28 | */ 29 | export default function TextListItem({ 30 | item, 31 | translate = true, 32 | }: { 33 | item: { value: string }; 34 | translate?: boolean; 35 | }) { 36 | const DARKMODE = useDarkMode(); 37 | const styles = StyleSheet.create({ 38 | listRowContainer: { 39 | flexDirection: 'row', 40 | alignItems: 'center', 41 | justifyContent: 'space-between', 42 | backgroundColor: DARKMODE ? colors.systemGray6Dark : colors.white, 43 | paddingVertical: 8, 44 | paddingHorizontal: 20, 45 | borderBottomWidth: 1, 46 | borderBottomColor: DARKMODE 47 | ? colors.systemGray5Dark 48 | : colors.systemGray5Light, 49 | }, 50 | listRowText: { 51 | color: DARKMODE ? colors.white : colors.black, 52 | paddingVertical: 5, 53 | }, 54 | linkImage: { 55 | height: 25, 56 | width: 25, 57 | borderRadius: 4, 58 | marginRight: 5, 59 | resizeMode: 'contain', 60 | }, 61 | linkText: { 62 | color: DARKMODE ? colors.purpleDark : colors.purpleLight, 63 | paddingRight: 5, 64 | flex: 1, 65 | }, 66 | }); 67 | 68 | return ( 69 | 70 | 71 | {!translate || item.value.includes('Alexander Burdiss') 72 | ? item.value 73 | : translateFunc(item.value)} 74 | 75 | 76 | ); 77 | } 78 | 79 | TextListItem.propTypes = { 80 | item: PropTypes.object, 81 | translate: PropTypes.bool, 82 | }; 83 | -------------------------------------------------------------------------------- /src/Components/ListItems/TextListItem/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './TextListItem'; 2 | -------------------------------------------------------------------------------- /src/Components/RandomizeButton/RandomizeButton.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import RandomizeButton from './RandomizeButton'; 4 | 5 | import { fireEvent, render } from '@testing-library/react-native'; 6 | 7 | test('RandomizeButton renders correctly', () => { 8 | const { queryByText } = render(); 9 | expect(queryByText(/Randomize/)).toBeTruthy(); 10 | }); 11 | 12 | test('RandomizeButton Function calls correctly', () => { 13 | const buttonHandler = jest.fn(); 14 | const { getByText } = render(); 15 | expect(buttonHandler).not.toHaveBeenCalled(); 16 | fireEvent.press(getByText(/Randomize/)); 17 | expect(buttonHandler).toHaveBeenCalledTimes(1); 18 | fireEvent.press(getByText(/Randomize/)); 19 | expect(buttonHandler).toHaveBeenCalledTimes(2); 20 | }); 21 | -------------------------------------------------------------------------------- /src/Components/RandomizeButton/RandomizeButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | Pressable, 5 | Text, 6 | StyleSheet, 7 | GestureResponderEvent, 8 | } from 'react-native'; 9 | import { useDarkMode } from '../../utils'; 10 | 11 | import { colors } from '../../Model/Model'; 12 | import { translate } from '../../Translations/TranslationModel'; 13 | 14 | /** 15 | * @function RandomizeButton 16 | * @component 17 | * @description A purple button meant to trigger the randomize process of the 18 | * app. Basic styles are already applied. 19 | * Created 10/11/20 20 | * @param {Object} props JSX props passed to this React component 21 | * @param {Function} props.handler The function to call when this component is 22 | * pressed 23 | * @returns {JSX.Element} JSX render instructions 24 | * 25 | * @copyright 2023 Alexander Burdiss 26 | * @author Alexander Burdiss 27 | * @since 9/23/23 28 | * @version 1.0.1 29 | * 30 | * @example 31 | * 32 | */ 33 | export default function RandomizeButton({ 34 | handler, 35 | accessibilityValue, 36 | accessibilityHint, 37 | accessible, 38 | }: { 39 | handler: (event: GestureResponderEvent) => null; 40 | accessibilityValue?: Object; 41 | accessibilityHint?: string; 42 | accessible?: boolean; 43 | }) { 44 | const DARKMODE = useDarkMode(); 45 | const styles = StyleSheet.create({ 46 | text: { 47 | textAlign: 'center', 48 | color: DARKMODE ? colors.purpleDark : colors.purpleLight, 49 | fontSize: 24, 50 | }, 51 | }); 52 | return ( 53 | ({ 59 | borderRadius: 8, 60 | borderColor: DARKMODE ? colors.purpleDark : colors.purpleLight, 61 | borderWidth: 1, 62 | margin: 10, 63 | paddingHorizontal: 10, 64 | paddingVertical: 5, 65 | justifyContent: 'center', 66 | opacity: pressed ? 0.7 : 1, 67 | overflow: 'hidden', 68 | })} 69 | accessibilityValue={accessibilityValue} 70 | accessibilityHint={accessibilityHint} 71 | accessible={accessible} 72 | > 73 | 74 | {translate('Randomize')} 75 | 76 | 77 | ); 78 | } 79 | 80 | RandomizeButton.propTypes = { 81 | handler: PropTypes.func, 82 | }; 83 | -------------------------------------------------------------------------------- /src/Components/RandomizeButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './RandomizeButton'; 2 | -------------------------------------------------------------------------------- /src/Components/ResetButton/ResetButton.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import ResetButton from './ResetButton'; 4 | 5 | import { fireEvent, render } from '@testing-library/react-native'; 6 | 7 | test('ResetButton renders correctly', () => { 8 | const { queryByText } = render(); 9 | expect(queryByText(/Reset/)).toBeTruthy(); 10 | }); 11 | 12 | test('ResetButton Function calls correctly', () => { 13 | const buttonHandler = jest.fn(); 14 | const { getByText } = render(); 15 | expect(buttonHandler).not.toHaveBeenCalled(); 16 | fireEvent.press(getByText(/Reset/)); 17 | expect(buttonHandler).toHaveBeenCalledTimes(1); 18 | fireEvent.press(getByText(/Reset/)); 19 | expect(buttonHandler).toHaveBeenCalledTimes(2); 20 | }); 21 | -------------------------------------------------------------------------------- /src/Components/ResetButton/ResetButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | View, 5 | Pressable, 6 | Text, 7 | StyleSheet, 8 | GestureResponderEvent, 9 | } from 'react-native'; 10 | import { useDarkMode } from '../../utils'; 11 | 12 | import { colors } from '../../Model/Model'; 13 | import { translate } from '../../Translations/TranslationModel'; 14 | 15 | /** 16 | * @function ResetButton 17 | * @component 18 | * @description A button that is styled to look like a reset button, with a red 19 | * outline and the text "Reset". 20 | * Created 11/18/20 21 | * @param {Object} props The JSX props passed to this React component 22 | * @param {Function} props.handler The function to call when this component 23 | * is pressed. 24 | * @returns {JSX.Element} JSX render instructions 25 | * 26 | * @copyright 2023 Alexander Burdiss 27 | * @author Alexander Burdiss 28 | * @since 9/23/23 29 | * @version 1.0.2 30 | * 31 | * @example 32 | * 33 | */ 34 | export default function ResetButton({ 35 | handler, 36 | }: { 37 | handler: (event: GestureResponderEvent) => void; 38 | }) { 39 | const DARKMODE = useDarkMode(); 40 | const styles = StyleSheet.create({ 41 | text: { 42 | textAlign: 'center', 43 | color: DARKMODE ? colors.redDark : colors.redLight, 44 | }, 45 | }); 46 | 47 | return ( 48 | 49 | ({ 57 | borderRadius: 8, 58 | borderColor: DARKMODE ? colors.redDark : colors.redLight, 59 | opacity: pressed ? 0.7 : 1, 60 | borderWidth: 1, 61 | margin: 10, 62 | padding: 12, 63 | overflow: 'hidden', 64 | })} 65 | onPress={handler} 66 | > 67 | {translate('Reset')} 68 | 69 | 70 | ); 71 | } 72 | 73 | ResetButton.propTypes = { 74 | handler: PropTypes.func, 75 | }; 76 | -------------------------------------------------------------------------------- /src/Components/ResetButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ResetButton'; 2 | -------------------------------------------------------------------------------- /src/Components/ScaleDisplay/ScaleDisplay.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import ScaleDisplay from './ScaleDisplay'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('ScaleDisplay renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/ScaleDisplay/ScaleDisplay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, Text, StyleSheet, Pressable, Alert } from 'react-native'; 4 | import { useNavigation } from '@react-navigation/native'; 5 | 6 | import { useDarkMode } from '../../utils'; 7 | 8 | import { colors } from '../../Model/Model'; 9 | import { translate } from '../../Translations/TranslationModel'; 10 | 11 | /** 12 | * @function ScaleDisplay 13 | * @component 14 | * @description A styled text box that shows the currently selected scale 15 | * Created 10/11/20 16 | * @param {Object} props JSX props passed to this React Component 17 | * @param {string} props.children The text to render inside this component 18 | * @returns {JSX.Element} JSX render instructions 19 | * 20 | * @copyright 2023 Alexander Burdiss 21 | * @author Alexander Burdiss 22 | * @since 9/23/23 23 | * @version 1.0.3 24 | * 25 | * @example 26 | * 27 | * Hello, World! 28 | * 29 | */ 30 | export default function ScaleDisplay({ children }: { children?: string }) { 31 | const DARKMODE = useDarkMode(); 32 | const navigation = useNavigation<{ navigate: Function }>(); 33 | const styles = StyleSheet.create({ 34 | container: { 35 | alignItems: 'center', 36 | justifyContent: 'flex-end', 37 | padding: 10, 38 | }, 39 | text: { 40 | backgroundColor: DARKMODE 41 | ? colors.systemGray2Dark 42 | : colors.systemGray2Light, 43 | color: DARKMODE ? colors.white : colors.black, 44 | overflow: 'hidden', 45 | textAlign: 'center', 46 | width: '100%', 47 | padding: 14, 48 | fontSize: 18, 49 | }, 50 | }); 51 | 52 | return ( 53 | 66 | 73 | 74 | {children} 75 | 76 | 77 | 78 | ); 79 | } 80 | 81 | ScaleDisplay.propTypes = { 82 | children: PropTypes.node, 83 | }; 84 | -------------------------------------------------------------------------------- /src/Components/ScaleDisplay/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ScaleDisplay'; 2 | -------------------------------------------------------------------------------- /src/Components/ScalePickers/ScalePickers.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import AndroidScalePickers from './ScalePickers.android'; 4 | import IosScalePickers from './ScalePickers.ios'; 5 | import MockContext from '../../../jest/MockContext'; 6 | 7 | import { render } from '@testing-library/react-native'; 8 | 9 | test('AndroidScalePickers renders correctly', () => { 10 | render( 11 | 12 | 20 | , 21 | ); 22 | }); 23 | 24 | test('IosScalePickers renders correctly', () => { 25 | render( 26 | 27 | 35 | , 36 | ); 37 | }); 38 | -------------------------------------------------------------------------------- /src/Components/ScalePickers/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ScalePickers'; 2 | -------------------------------------------------------------------------------- /src/Components/SwipeableRow/SwipeableRow.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import SwipeableRow from './SwipeableRow'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('SwipeableRow renders correctly', () => { 9 | render( 10 | 11 | 16 | , 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Components/SwipeableRow/SwipeableRow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { RectButton, Swipeable } from 'react-native-gesture-handler'; 4 | import Ionicons from 'react-native-vector-icons/Ionicons'; 5 | 6 | import { colors } from '../../Model/Model'; 7 | // import { translate } from '../../Translations/TranslationModel'; 8 | 9 | /** 10 | * @function SwipeableRow 11 | * @component 12 | * @description A swipeable row with a right action. 13 | * Created 11/7/2020 14 | * @param {Object} props JSX props passed to this React component 15 | * @param {*} props.children The children to render in this row 16 | * @param {Object} props.styles additional styles to add to this component 17 | * @param {Function} props.deleteItem A function to call to delete this item 18 | * @param {string} props.item The item data 19 | * @returns {JSX.Element} JSX render instructions 20 | * 21 | * @copyright 2023 Alexander Burdiss 22 | * @author Alexander Burdiss 23 | * @since 9/23/23 24 | * @version 2.0.0 25 | * 26 | * @example 27 | * 28 | * {..} 29 | * 30 | */ 31 | export default function SwipeableRow({ 32 | children, 33 | styles, 34 | deleteItem, 35 | item, 36 | }: { 37 | children?: React.ReactNode; 38 | styles: { rightAction: Object; trashIcon: Object }; 39 | deleteItem: Function; 40 | item: Object; 41 | }) { 42 | const renderRightActions = () => { 43 | return ( 44 | deleteItem(item)} 47 | // accessibilityRole="button" 48 | // accessibilityLabel={translate('Delete')} 49 | > 50 | 56 | 57 | ); 58 | }; 59 | 60 | return ( 61 | 67 | {children} 68 | 69 | ); 70 | } 71 | 72 | SwipeableRow.propTypes = { 73 | children: PropTypes.node, 74 | styles: PropTypes.object, 75 | deleteItem: PropTypes.func, 76 | item: PropTypes.object, 77 | }; 78 | -------------------------------------------------------------------------------- /src/Components/SwipeableRow/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SwipeableRow'; 2 | -------------------------------------------------------------------------------- /src/Components/SwitchRow/SwitchRow.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import SwitchRow from './SwitchRow'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('SwitchRow renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Components/SwitchRow/SwitchRow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Text, Switch, Pressable, StyleSheet } from 'react-native'; 4 | 5 | import { colors } from '../../Model/Model'; 6 | import { translate } from '../../Translations/TranslationModel'; 7 | import { useDarkMode } from '../../utils'; 8 | 9 | /** 10 | * @function SwitchRow 11 | * @component 12 | * @description One Switch Row that is used on the Scale and Arpeggio Display 13 | * views. This view was created to create a more accessible and reuseable 14 | * switch row. 15 | * Created 1/5/21 16 | * @param {Object} props JSX props passed to this React component 17 | * @param {boolean} props.value The current value of the switch 18 | * @param {Function} props.onValueChange The function to call when the value 19 | * changes. 20 | * @param {string} props.text The text to render on this switch row. 21 | * @returns {JSX.Element} JSX render instructions. 22 | * 23 | * @copyright 2023 Alexander Burdiss 24 | * @author Alexander Burdiss 25 | * @since 9/23/23 26 | * @version 1.0.2 27 | * @example 28 | * 33 | */ 34 | export default function SwitchRow({ 35 | value, 36 | onValueChange, 37 | text, 38 | }: { 39 | value: boolean; 40 | onValueChange: (newValue: any) => void; 41 | text: string; 42 | }) { 43 | const DARKMODE = useDarkMode(); 44 | const styles = StyleSheet.create({ 45 | switchRow: { 46 | flexDirection: 'row', 47 | justifyContent: 'space-between', 48 | alignItems: 'center', 49 | paddingVertical: 4, 50 | paddingHorizontal: 40, 51 | }, 52 | switchText: { 53 | color: DARKMODE ? colors.white : colors.black, 54 | }, 55 | }); 56 | 57 | return ( 58 | 66 | {text} 67 | 68 | 69 | ); 70 | } 71 | 72 | SwitchRow.propTypes = { 73 | value: PropTypes.bool, 74 | onValueChange: PropTypes.func, 75 | text: PropTypes.string, 76 | }; 77 | -------------------------------------------------------------------------------- /src/Components/SwitchRow/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SwitchRow'; 2 | -------------------------------------------------------------------------------- /src/Model/AcknowledgementsModel.ts: -------------------------------------------------------------------------------- 1 | export const TRANSLATIONS = Object.freeze([ 2 | { 3 | id: '1', 4 | value: 'Courtney Carmack', 5 | }, 6 | { 7 | id: '2', 8 | value: 'Mishi Laplante', 9 | }, 10 | { 11 | id: '3', 12 | value: 'Qian Yu', 13 | }, 14 | ]); 15 | -------------------------------------------------------------------------------- /src/Model/Model.d.ts: -------------------------------------------------------------------------------- 1 | import { APP_DATA_TYPES } from '../enums/appDataTypes'; 2 | 3 | export type PreferencesStateType = { 4 | repeat: boolean; 5 | simpleRandom: boolean; 6 | disableScreenSleep: boolean; 7 | randomType: APP_DATA_TYPES; 8 | resourcesType: APP_DATA_TYPES; 9 | advancedType: APP_DATA_TYPES; 10 | }; 11 | -------------------------------------------------------------------------------- /src/Model/Model.test.ts: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | 3 | import fetch from 'node-fetch'; 4 | 5 | import { scaleResourceData, arpeggioResourceData, getImagePath } from './Model'; 6 | import { RESOURCES, ABOUT } from './MoreModel'; 7 | 8 | test.each(scaleResourceData)('all scale images exist', (item) => { 9 | expect(getImagePath(item.id)).not.toBe(null); 10 | }); 11 | 12 | test.each(arpeggioResourceData)('all arpeggio images exist', (item) => { 13 | expect(getImagePath(item.id)).not.toBe(null); 14 | }); 15 | 16 | // eslint-disable-next-line jest/no-disabled-tests 17 | describe.skip('links', () => { 18 | RESOURCES.map(async (resource) => { 19 | test('resources links', async () => { 20 | if (resource.type == 'link') { 21 | const resp = await fetch(resource.link); 22 | expect(resp.status).toEqual(200); 23 | } 24 | }); 25 | }); 26 | 27 | ABOUT.map(async (about) => { 28 | test('about links', async () => { 29 | if ( 30 | about.type == 'link' && 31 | !!about.link && 32 | !about.link.startsWith('mailto') 33 | ) { 34 | const resp = await fetch(about.link); 35 | expect(resp.status).toEqual(200); 36 | } 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/Model/MoreModel.ts: -------------------------------------------------------------------------------- 1 | import DeviceInfo from 'react-native-device-info'; 2 | 3 | const GOOGLE_PLAY_LINK = 4 | 'https://play.google.com/store/apps/developer?id=Alexander+Burdiss'; 5 | const APPLE_STORE_LINK = 6 | 'https://apps.apple.com/us/developer/alexander-burdiss/id1496727055'; 7 | const AMAZON_STORE_LINK = 8 | 'https://www.amazon.com/s?i=mobile-apps&rh=p_4%3AAlexander+Burdiss'; 9 | 10 | export const SETTINGS = [ 11 | { 12 | id: '0', 13 | type: 'switch', 14 | value: 'Repeat Scales', 15 | setting: 'repeat', 16 | }, 17 | { 18 | id: '1', 19 | type: 'switch', 20 | value: 'Simple Random Display', 21 | setting: 'simpleRandom', 22 | }, 23 | { 24 | id: '1A', 25 | type: 'switch', 26 | value: 'Keep Screen On', 27 | setting: 'disableScreenSleep', 28 | }, 29 | ]; 30 | 31 | export const RESOURCES = [ 32 | { 33 | id: '2', 34 | type: 'link', 35 | value: 'More Apps by Alexander Burdiss', 36 | image: require('../../img/BrassRoutinesIcon.png'), 37 | link: 38 | DeviceInfo.getBrand() === 'Apple' 39 | ? APPLE_STORE_LINK 40 | : DeviceInfo.getBrand() === 'Amazon' 41 | ? AMAZON_STORE_LINK 42 | : GOOGLE_PLAY_LINK, 43 | }, 44 | { 45 | id: '3', 46 | type: 'link', 47 | value: 'Visit Ars Nova Publishing', 48 | image: require('../../img/ANPLogo.png'), 49 | link: 'https://www.arsnovapublishing.com/', 50 | }, 51 | { 52 | id: '4', 53 | type: 'link', 54 | value: 'Visit Band Room Online', 55 | link: 'https://www.bandroomonline.com/', 56 | }, 57 | ]; 58 | 59 | export const HELP = [ 60 | { 61 | id: '10', 62 | type: 'navigate', 63 | value: 'How to use this app', 64 | component: 'Help', 65 | }, 66 | { 67 | id: '8', 68 | type: 'link', 69 | value: 'Send Feedback', 70 | link: 'mailto:scalepractice@alexanderburdiss.com?subject=Scale%20Practice%20Feedback', 71 | }, 72 | ]; 73 | 74 | export const ABOUT = [ 75 | { 76 | id: '5', 77 | type: 'text', 78 | value: `© ${new Date().getFullYear()} ` + 'Alexander Burdiss', 79 | }, 80 | { 81 | id: '11', 82 | type: 'navigate', 83 | value: 'Statistics', 84 | component: 'Statistics', 85 | }, 86 | { 87 | id: '6', 88 | type: 'navigate', 89 | value: 'Acknowledgements', 90 | component: 'Acknowledgements', 91 | }, 92 | { 93 | id: '7', 94 | type: 'navigate', 95 | value: 'Licenses', 96 | component: 'Licenses', 97 | }, 98 | { 99 | id: '9', 100 | type: 'link', 101 | value: 'Open Source', 102 | link: 'https://github.com/aburdiss/ScalePractice', 103 | }, 104 | ]; 105 | -------------------------------------------------------------------------------- /src/Navigation/AdvancedStack.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { useDarkMode } from '../utils'; 3 | import { createStackNavigator } from '@react-navigation/stack'; 4 | 5 | import AdvancedScale from '../Screens/Advanced/Advanced'; 6 | 7 | import HeaderButton from '../Components/HeaderButton'; 8 | 9 | import { translate } from '../Translations/TranslationModel'; 10 | import { colors } from '../Model/Model'; 11 | import { PreferencesContext, preferencesActions } from '../Model/Preferences'; 12 | import { APP_DATA_TYPES } from '../enums/appDataTypes'; 13 | 14 | const Stack = createStackNavigator(); 15 | 16 | /** 17 | * @description The stack of screens for the Advanced tab of the navigation. 18 | * Created 10/10/20 by Alexander Burdiss 19 | * @author Alexander Burdiss 20 | * @since 10/25/22 21 | * @version 1.1.1 22 | * 23 | * @example 24 | * 29 | */ 30 | export default function AdvancedStack() { 31 | const DARKMODE = useDarkMode(); 32 | 33 | const { state, dispatch } = useContext(PreferencesContext); 34 | 35 | const isScale = state?.advancedType == APP_DATA_TYPES.SCALE; 36 | 37 | return ( 38 | 55 | 65 | 66 | ); 67 | } 68 | 69 | function getHeaderRight(isScale: boolean, dispatch: Function) { 70 | return () => ( 71 | { 73 | const newType = isScale 74 | ? APP_DATA_TYPES.ARPEGGIO 75 | : APP_DATA_TYPES.SCALE; 76 | dispatch({ 77 | type: preferencesActions.SET_SETTING, 78 | payload: { advancedType: newType }, 79 | }); 80 | }} 81 | > 82 | {isScale ? translate('Arpeggios') : translate('Scales')} 83 | 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/Navigation/RandomStack.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | import { useDarkMode } from '../utils'; 4 | 5 | import Random from '../Screens/Random'; 6 | import HeaderButton from '../Components/HeaderButton'; 7 | import { translate } from '../Translations/TranslationModel'; 8 | import { colors } from '../Model/Model'; 9 | import { PreferencesContext, preferencesActions } from '../Model/Preferences'; 10 | import { APP_DATA_TYPES } from '../enums/appDataTypes'; 11 | 12 | const Stack = createStackNavigator(); 13 | 14 | /** 15 | * @description The stack of screens for the Random Tab of the navigation. 16 | * Created 10/10/20 17 | * @copyright Alexander Burdiss 18 | * @author Alexander Burdiss 19 | * @since 10/25/22 20 | * @version 1.0.2 21 | * 22 | * @example 23 | * 28 | */ 29 | export default function RandomStack() { 30 | const DARKMODE = useDarkMode(); 31 | 32 | const { state, dispatch } = useContext(PreferencesContext); 33 | 34 | const isScale = state?.randomType == APP_DATA_TYPES.SCALE; 35 | 36 | return ( 37 | 54 | 64 | 65 | ); 66 | } 67 | 68 | function getHeaderRight(isScale: boolean, dispatch: Function) { 69 | return () => ( 70 | { 72 | const newType = isScale 73 | ? APP_DATA_TYPES.ARPEGGIO 74 | : APP_DATA_TYPES.SCALE; 75 | dispatch({ 76 | type: preferencesActions.SET_SETTING, 77 | payload: { randomType: newType }, 78 | }); 79 | }} 80 | > 81 | {isScale ? translate('Arpeggios') : translate('Scales')} 82 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/Screens/Acknowledgements/Acknowledgements.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Acknowledgements from './Acknowledgements'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('Acknowledgements renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Acknowledgements/Acknowledgements.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SectionList, Text, View, StyleSheet } from 'react-native'; 3 | 4 | import TextListItem from '../../Components/ListItems/TextListItem'; 5 | 6 | import { colors } from '../../Model/Model'; 7 | import { TRANSLATIONS } from '../../Model/AcknowledgementsModel'; 8 | import { translate } from '../../Translations/TranslationModel'; 9 | import { useDarkMode, useIdleScreen } from '../../utils'; 10 | 11 | /** 12 | * @function Acknowledgements 13 | * @component 14 | * @description A View that displays the people who directly assisted with 15 | * this project 16 | * Created 10/25/2022 17 | * @returns {JSX.Element} JSX render instructions 18 | * 19 | * @copyright 2025 Alexander Burdiss 20 | * @author Alexander Burdiss 21 | * @since 1/30/25 22 | * @version 1.1.0 23 | * 24 | * @example 25 | * 26 | */ 27 | export default function Acknowledgements() { 28 | useIdleScreen(); 29 | 30 | const DARKMODE = useDarkMode(); 31 | const styles = StyleSheet.create({ 32 | listHeader: { 33 | textTransform: 'uppercase', 34 | paddingLeft: 20, 35 | paddingTop: 30, 36 | paddingBottom: 10, 37 | color: DARKMODE ? colors.systemGray : colors.systemGray, 38 | }, 39 | sectionList: { 40 | height: '100%', 41 | backgroundColor: DARKMODE ? colors.black : colors.systemGray6Light, 42 | }, 43 | iconContainer: { 44 | flexDirection: 'row', 45 | }, 46 | icon: { 47 | paddingHorizontal: 5, 48 | }, 49 | footerContainer: { 50 | paddingTop: 30, 51 | alignItems: 'center', 52 | }, 53 | footerText: { 54 | color: colors.systemGray, 55 | paddingTop: 10, 56 | paddingBottom: 30, 57 | }, 58 | }); 59 | 60 | return ( 61 | 62 | String(index)} 65 | renderItem={({ item }) => } 66 | renderSectionHeader={({ section: { title } }) => ( 67 | {title} 68 | )} 69 | stickySectionHeadersEnabled={false} 70 | /> 71 | 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/Screens/Advanced/Advanced.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Advanced from './Advanced'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('Advanced renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Advanced/utils/getAdvancedReducer/getAdvancedReducer.test.ts: -------------------------------------------------------------------------------- 1 | import { APP_DATA_TYPES } from '../../../../enums/appDataTypes'; 2 | import { 3 | getAdvancedReducer, 4 | ADVANCED_ACTIONS, 5 | INITIAL_ADVANCED_STATE, 6 | } from './index'; 7 | 8 | const mockState = { 9 | randomType: APP_DATA_TYPES.SCALE, 10 | repeat: false, 11 | simpleRandom: false, 12 | disableScreenSleep: false, 13 | resourcesType: APP_DATA_TYPES.SCALE, 14 | advancedType: APP_DATA_TYPES.SCALE, 15 | }; 16 | 17 | describe('getAdvancedReducer functions correctly', () => { 18 | const advancedReducer = getAdvancedReducer(mockState, jest.fn()); 19 | describe('All actions handled by reducer', () => { 20 | (Object.keys(ADVANCED_ACTIONS) as Array).map((action) => { 21 | test(action, () => { 22 | advancedReducer(INITIAL_ADVANCED_STATE, { 23 | type: action, 24 | payload: 'major', 25 | }); 26 | }); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/Screens/Help/Help.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Help from './Help'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('Help renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Help/Help.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View, StyleSheet } from 'react-native'; 3 | import { useDarkMode, useIdleScreen } from '../../utils'; 4 | import { colors } from '../../Model/Model'; 5 | import { translate } from '../../Translations/TranslationModel'; 6 | 7 | /** 8 | * @function Help 9 | * @component 10 | * @description The Help Page component for the Scale Practice App 11 | * Created 8/25/23 12 | * @returns {JSX.Element} JSX render instructions 13 | * 14 | * @copyright 2025 Alexander Burdiss 15 | * @since 1/30/25 16 | * @version 1.0.1 17 | * @example 18 | * 19 | */ 20 | export default function Help() { 21 | useIdleScreen(); 22 | 23 | const DARKMODE = useDarkMode(); 24 | const { container, text } = StyleSheet.create({ 25 | container: { 26 | paddingHorizontal: 20, 27 | backgroundColor: DARKMODE ? colors.black : colors.systemGray6Light, 28 | height: '100%', 29 | }, 30 | text: { paddingTop: 20, color: DARKMODE ? colors.white : colors.black }, 31 | }); 32 | 33 | return ( 34 | 35 | {translate('Help1')} 36 | {translate('Help2')} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/Screens/Help/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Help'; 2 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesLink/LicensesLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Text, Linking } from 'react-native'; 4 | /** 5 | * @function LicensesLink 6 | * @component 7 | * @memberof Licenses 8 | * @description One link item that opens the main software link in the 9 | * LicensesListItem component. Text is limited to one line. 10 | * [Created with help from an online article]{@link https://blog.expo.io/licenses-the-best-part-of-your-app-29e7285b544f} 11 | * Created 12/17/20 12 | * @param {Object} props JSX props passed to this React component 13 | * @param {string} props.url The url to open when the element is tapped. 14 | * @param {Object} props.style Style to be applied to the element 15 | * @param {string} props.children Text to be rendered inside this element. 16 | * @returns {JSX.Element} JSX render instructions 17 | * 18 | * @copyright 2025 Alexander Burdiss 19 | * @author Alexander Burdiss 20 | * @since 1/30/25 21 | * @version 1.0.2 22 | * 23 | * @example 24 | * 25 | * {licenses} 26 | * 27 | */ 28 | export default function LicensesLink({ 29 | url, 30 | style, 31 | children, 32 | }: { 33 | url: string; 34 | style: Object; 35 | children: React.ReactNode; 36 | }) { 37 | return ( 38 | url && Linking.openURL(url)} 42 | > 43 | {children} 44 | 45 | ); 46 | } 47 | 48 | LicensesLink.propTypes = { 49 | url: PropTypes.string, 50 | style: PropTypes.object, 51 | children: PropTypes.node, 52 | }; 53 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesLink/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LicensesLink'; 2 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesList/LicensesList.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import LicensesList from './LicensesList'; 4 | import MockContext from '../../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('LicensesList renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesList/LicensesList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FlatList, StyleSheet } from 'react-native'; 4 | import LicensesListItem from '../LicensesListItem'; 5 | 6 | const styles = StyleSheet.create({ 7 | list: { 8 | flex: 1, 9 | }, 10 | }); 11 | 12 | /** 13 | * @function LicensesList 14 | * @component 15 | * @memberof Licenses 16 | * @description The list of licenses of dependencies used in this app. 17 | * [Created with help from an online article]{@link https://blog.expo.io/licenses-the-best-part-of-your-app-29e7285b544f} 18 | * Created 12/17/2020 19 | * @param {Object[]} props.licenses The list of licenses that will be displayed. 20 | * @returns {JSX.Element} JSX render instructions 21 | * 22 | * @copyright 2025 Alexander Burdiss 23 | * @author Alexander Burdiss 24 | * @since 1/30/25 25 | * @version 1.0.1 26 | * 27 | * @example 28 | * 29 | */ 30 | export default function LicensesList({ 31 | licenses, 32 | }: { 33 | licenses?: { 34 | key: any; 35 | image: string; 36 | userUrl: string; 37 | username: string; 38 | name: string; 39 | version: string; 40 | licenses: string; 41 | repository: string; 42 | licenseUrl: string; 43 | }[]; 44 | }) { 45 | return ( 46 | key} 49 | data={licenses} 50 | renderItem={({ item }) => } 51 | /> 52 | ); 53 | } 54 | 55 | LicensesList.propTypes = { 56 | licenses: PropTypes.arrayOf(PropTypes.object), 57 | }; 58 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesList/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LicensesList'; 2 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesListItem/LicensesListItem.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import LicensesListItem from './LicensesListItem'; 4 | import MockContext from '../../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('LicensesListItem renders correctly', () => { 9 | render( 10 | 11 | 21 | , 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Components/LicensesListItem/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LicensesListItem'; 2 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Licenses.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Licenses from './Licenses'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('Licenses renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Licenses/Licenses.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | 4 | import LicensesList from './Components/LicensesList'; 5 | import { colors } from '../../Model/Model'; 6 | import { licenseData } from './licenseData'; 7 | 8 | import { useIdleScreen, useDarkMode } from '../../utils'; 9 | 10 | /** 11 | * @namespace Licenses 12 | * @description A group of Components and Functions used to show Licenses for 13 | * all the packages I use in this application. 14 | */ 15 | 16 | /** 17 | * @function Licenses 18 | * @component 19 | * @memberof Licenses 20 | * @description A wrapper for the LicensesList component that processes the 21 | * data and passes it in. 22 | * Created 2/1/2021 23 | * [Created with help from an online article]{@link https://blog.expo.io/licenses-the-best-part-of-your-app-29e7285b544f} 24 | * @returns {JSX.Element} JSX render instructions 25 | * 26 | * @copyright 2025 Alexander Burdiss 27 | * @author Alexander Burdiss 28 | * @since 1/30/25 29 | * @version 1.2.1 30 | * 31 | * @example 32 | * 33 | */ 34 | export default function Licenses() { 35 | useIdleScreen(); 36 | 37 | const DARKMODE = useDarkMode(); 38 | 39 | const styles = StyleSheet.create({ 40 | wrapper: { 41 | flex: 1, 42 | backgroundColor: DARKMODE ? colors.black : colors.systemGray2Light, 43 | }, 44 | }); 45 | 46 | return ( 47 | 48 | 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/Screens/Licenses/licenseData.ts: -------------------------------------------------------------------------------- 1 | import rawLicenseData from './licenses.json'; 2 | import { capitalize } from '../../utils'; 3 | 4 | import { extractNameFromGithubUrl } from './utils/extractNameFromGithubUrl'; 5 | import { sortDataByKey } from './utils/sortDataByKey'; 6 | 7 | let licenseData = Object.keys(rawLicenseData).map((key) => { 8 | // @ts-ignore 9 | let { licenses, ...license } = rawLicenseData[key]; 10 | 11 | let name, version; 12 | if (key[0] == '@') { 13 | [, name, version] = key.split('@'); 14 | } else { 15 | [name, version] = key.split('@'); 16 | } 17 | 18 | let username = 19 | extractNameFromGithubUrl(license.repository) || 20 | extractNameFromGithubUrl(license.licenseUrl); 21 | 22 | let userUrl; 23 | let image; 24 | if (username) { 25 | username = capitalize(username); 26 | image = `http://github.com/${username}.png`; 27 | userUrl = `http://github.com/${username}`; 28 | } 29 | 30 | return { 31 | key, 32 | name, 33 | image, 34 | userUrl, 35 | username, 36 | licenses: licenses.slice(0, 405), 37 | version, 38 | ...license, 39 | }; 40 | }); 41 | 42 | licenseData = sortDataByKey(licenseData, 'username'); 43 | 44 | export { licenseData }; 45 | -------------------------------------------------------------------------------- /src/Screens/Licenses/utils/extractNameFromGithubUrl/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @function extractNameFromGithubUrl 3 | * @memberof Licenses 4 | * @description Takes a url to a gitHub repository and returns the username of 5 | * the author of the software. 6 | * [Created with help from an online article]{@link https://blog.expo.io/licenses-the-best-part-of-your-app-29e7285b544f} 7 | * Created 12/17/20 8 | * @param {string} url The GitHub url of a piece of software. 9 | * @returns {string} The GitHub username 10 | * 11 | * @copyright 2025 Alexander Burdiss 12 | * @author Alexander Burdiss 13 | * @version 1.0.2 14 | * @since 1/30/25 15 | */ 16 | export function extractNameFromGithubUrl(url?: string) { 17 | if (!url) { 18 | return null; 19 | } 20 | 21 | const reg = 22 | /((https?:\/\/)?(www\.)?github\.com\/)?(@|#!\/)?([A-Za-z0-9_-]{1,30})(\/([-a-z]{1,40}))?/i; 23 | 24 | const components = reg.exec(url); 25 | 26 | if (components && components.length > 5) { 27 | return components[5]; 28 | } 29 | return null; 30 | } 31 | -------------------------------------------------------------------------------- /src/Screens/Licenses/utils/sortDataByKey/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @function sortDataByKey 3 | * @memberof Licenses 4 | * @description Sorts the licenses data by key. 5 | * [Created with help from an online article]{@link https://blog.expo.io/licenses-the-best-part-of-your-app-29e7285b544f} 6 | * Created 12/17/2020 7 | * @param {Object[]} data The list of licenses. 8 | * @param {string|number} key An object key inside each member of data. 9 | * @returns {Object[]} A sorted version of the data array that is passed in. 10 | * @copyright 2025 Alexander Burdiss 11 | * @author Alexander Burdiss 12 | * @since 1/30/25 13 | * @version 1.0.2 14 | */ 15 | export function sortDataByKey(data: { [key: string]: any }[], key: string) { 16 | const tempData = [...data]; 17 | tempData.sort(function (a, b) { 18 | return a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0; 19 | }); 20 | return tempData; 21 | } 22 | -------------------------------------------------------------------------------- /src/Screens/More/More.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import More from './More'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('More renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Random/Components/RandomSettings/RandomSettings.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import RandomSettings from './RandomSettings'; 4 | import MockContext from '../../../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | import { INITIAL_RANDOM_STATE } from '../../utils/getRandomReducer'; 8 | import { RANDOM_ACTIONS } from '../../enums/randomActions'; 9 | 10 | test('RandomSettings renders correctly', () => { 11 | render( 12 | 13 | 19 | , 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /src/Screens/Random/Components/RandomSettings/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './RandomSettings'; 2 | -------------------------------------------------------------------------------- /src/Screens/Random/Random.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name RandomStateType 3 | * @type 4 | * @memberof Random 5 | * @description The Type information for the Object stored in the Random Screen 6 | * Reducer. 7 | * Created 1/31/25 by Alexander Burdiss 8 | * 9 | * @copyright 2025 Alexander Burdiss 10 | * @author Alexander Burdiss 11 | * @since 1/31/25 12 | * @version 1.0.0 13 | */ 14 | export type RandomStateType = { 15 | currentScale: string; 16 | scaleArray: string[]; 17 | scaleArrayIndex: number; 18 | showSelectionPopover: boolean; 19 | allScalesPracticed: boolean; 20 | scaleOptions: { 21 | [key: string]: boolean; 22 | }; 23 | arpeggioOptions: { 24 | [key: string]: boolean; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/Screens/Random/Random.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Random from './Random'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('Random renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Random/enums/randomActions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum {string} 3 | * @name RANDOM_ACTIONS 4 | * @memberof Random 5 | * @description An enum of the different actions available to take in the 6 | * Random Reducer. 7 | * Created 1/31/25 by Alexander Burdiss 8 | * 9 | * @copyright 2025 Alexander Burdiss 10 | * @author Alexander Burdiss 11 | * @since 1/31/25 12 | * @version 1.0.0 13 | */ 14 | export enum RANDOM_ACTIONS { 15 | SET_CURRENT_SCALE = 'SET_CURRENT_SCALE', 16 | TOGGLE_SCALE = 'TOGGLE_SCALE', 17 | TOGGLE_ARPEGGIO = 'TOGGLE_ARPEGGIO', 18 | SELECT_ALL_SCALES = 'SELECT_ALL_SCALES', 19 | SELECT_ALL_ARPEGGIOS = 'SELECT_ALL_ARPEGGIOS', 20 | GET_NEW_SCALE = 'GET_NEW_SCALE', 21 | RESET_NO_REPEAT = 'RESET_NO_REPEAT', 22 | TOGGLE_SELECTION_POPOVER = 'TOGGLE_SELECTION_POPOVER', 23 | SWITCH_DOMAIN = 'SWITCH_DOMAIN', 24 | SET_STATE_FROM_STORAGE = 'SET_STATE_FROM_STORAGE', 25 | } 26 | -------------------------------------------------------------------------------- /src/Screens/Random/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Random'; 2 | -------------------------------------------------------------------------------- /src/Screens/Random/utils/getAllArpeggiosFromState/index.ts: -------------------------------------------------------------------------------- 1 | import { createArpeggioArrayFromParts } from '../../../../utils'; 2 | 3 | /** 4 | * @function getAllArpeggiosFromState 5 | * @memberof Random 6 | * @description This function will create an array of all the possible arpeggios 7 | * based on the selected arpeggios from state. 8 | * Created 10/8/2022 9 | * @param {Object} arpeggioOptions The options stored in state. 10 | * @returns {string[]} An array of all the possible arpeggios based on the 11 | * passed in state. 12 | * 13 | * @copyright 2025 Alexander Burdiss 14 | * @author Alexander Burdiss 15 | * @since 1/30/25 16 | * @version 1.0.0 17 | * @example const arpeggios = getAllArpeggiosFromState(state.arpeggioOptions); 18 | */ 19 | export function getAllArpeggiosFromState(arpeggioOptions: { 20 | [key: string]: boolean; 21 | }): string[] { 22 | let possibleArpeggios = []; 23 | 24 | if (arpeggioOptions.major) { 25 | possibleArpeggios.push(...createArpeggioArrayFromParts('Major')); 26 | } 27 | if (arpeggioOptions.minor) { 28 | possibleArpeggios.push(...createArpeggioArrayFromParts('Minor')); 29 | } 30 | if (arpeggioOptions.augmented) { 31 | possibleArpeggios.push(...createArpeggioArrayFromParts('Augmented')); 32 | } 33 | if (arpeggioOptions.diminished) { 34 | possibleArpeggios.push(...createArpeggioArrayFromParts('Diminished')); 35 | } 36 | if (arpeggioOptions.dominantSeventh) { 37 | possibleArpeggios.push(...createArpeggioArrayFromParts('Dominant Seventh')); 38 | } 39 | if (arpeggioOptions.majorSeventh) { 40 | possibleArpeggios.push(...createArpeggioArrayFromParts('Major Seventh')); 41 | } 42 | if (arpeggioOptions.minorSeventh) { 43 | possibleArpeggios.push(...createArpeggioArrayFromParts('Minor Seventh')); 44 | } 45 | if (arpeggioOptions.minorMajorSeventh) { 46 | possibleArpeggios.push( 47 | ...createArpeggioArrayFromParts('Minor Major Seventh'), 48 | ); 49 | } 50 | if (arpeggioOptions.augmentedSeventh) { 51 | possibleArpeggios.push( 52 | ...createArpeggioArrayFromParts('Augmented Minor Seventh'), 53 | ); 54 | } 55 | if (arpeggioOptions.halfDiminishedSeventh) { 56 | possibleArpeggios.push( 57 | ...createArpeggioArrayFromParts('Half Diminished Seventh'), 58 | ); 59 | } 60 | if (arpeggioOptions.diminishedSeventh) { 61 | possibleArpeggios.push( 62 | ...createArpeggioArrayFromParts('Diminished Seventh'), 63 | ); 64 | } 65 | return possibleArpeggios; 66 | } 67 | -------------------------------------------------------------------------------- /src/Screens/Random/utils/getRandomReducer/getRandomReducer.test.ts: -------------------------------------------------------------------------------- 1 | import { getRandomReducer, INITIAL_RANDOM_STATE } from './index'; 2 | import { RANDOM_ACTIONS } from '../../enums/randomActions'; 3 | import { APP_DATA_TYPES } from '../../../../enums/appDataTypes'; 4 | 5 | const mockState = { 6 | randomType: APP_DATA_TYPES.SCALE, 7 | repeat: false, 8 | simpleRandom: false, 9 | disableScreenSleep: false, 10 | resourcesType: APP_DATA_TYPES.SCALE, 11 | advancedType: APP_DATA_TYPES.SCALE, 12 | }; 13 | 14 | describe('getRandomReducer functions correctly', () => { 15 | const randomReducer = getRandomReducer(mockState, jest.fn()); 16 | describe('All actions handled by reducer', () => { 17 | (Object.keys(RANDOM_ACTIONS) as Array).map((action) => { 18 | test(String(action), () => { 19 | randomReducer(INITIAL_RANDOM_STATE, { 20 | type: action, 21 | payload: 'major', 22 | }); 23 | }); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/Screens/Random/utils/getTranslationKeyFromStateKey/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @function getTranslationKeyFromStateKey 3 | * @memberof Random 4 | * @description Transforms a key that is used in the global state reducer to a 5 | * key that is used in the translation files. 6 | * Created 10/7/2022 by Alexander Burdiss 7 | * @param {string} value The key for the value in state 8 | * @param {boolean} isScale Whether or not the inputted key is a scale. 9 | * @returns {string} A key that you can pass to the "translate" function 10 | * 11 | * @copyright 2025 Alexander Burdiss 12 | * @author Alexander Burdiss 13 | * @since 1/31/25 14 | * @version 1.0.0 15 | */ 16 | export function getTranslationKeyFromStateKey( 17 | value: string, 18 | isScale: boolean, 19 | ): string | undefined { 20 | if (isScale) { 21 | return { 22 | major: 'Major', 23 | naturalMinor: 'Natural Minor', 24 | harmonicMinor: 'Harmonic Minor', 25 | melodicMinor: 'Melodic Minor', 26 | majorModes: 'Major Modes', 27 | melodicMinorModes: 'Melodic Minor Modes', 28 | blues: 'Blues', 29 | pentatonic: 'Pentatonic', 30 | octatonic: 'Octatonic', 31 | wholeTone: 'Whole Tone', 32 | }[value]; 33 | } else { 34 | // Arpeggio 35 | return { 36 | major: 'MajorChord', 37 | minor: 'Minor', 38 | augmented: 'Augmented', 39 | diminished: 'Diminished', 40 | dominantSeventh: 'Dominant Seventh', 41 | majorSeventh: 'Major Seventh', 42 | minorSeventh: 'Minor Seventh', 43 | minorMajorSeventh: 'Minor Major Seventh', 44 | augmentedSeventh: 'Augmented Minor Seventh', 45 | halfDiminishedSeventh: 'Half Diminished Seventh', 46 | diminishedSeventh: 'Diminished Seventh', 47 | }[value]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Screens/Resources/Resources.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Resources from './Resources'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('Resources renders correctly', () => { 9 | render( 10 | 11 | 12 | , 13 | ); 14 | }); 15 | -------------------------------------------------------------------------------- /src/Screens/Resources/Resources.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { View, FlatList } from 'react-native'; 3 | 4 | import FlatListItem from '../../Components/ListItems/FlatListItem'; 5 | import { 6 | scaleResourceData, 7 | arpeggioResourceData, 8 | colors, 9 | } from '../../Model/Model'; 10 | import { PreferencesContext } from '../../Model/Preferences'; 11 | import { useIdleScreen, useDarkMode } from '../../utils'; 12 | import { APP_DATA_TYPES } from '../../enums/appDataTypes'; 13 | 14 | /** 15 | * @function Resources 16 | * @component 17 | * @description A view that allows the user to learn more about each scale in 18 | * the app. 19 | * Created By Alexander Burdiss 10/10/202 20 | * @returns {JSX.Element} JSX render instructions 21 | * 22 | * @copyright 2025 Alexander Burdiss 23 | * @author Alexander Burdiss 24 | * @since 2/1/25 25 | * @version 2.1.1 26 | * 27 | * @example 28 | * 29 | */ 30 | export default function Resources() { 31 | useIdleScreen(); 32 | 33 | const DARKMODE = useDarkMode(); 34 | 35 | const { state } = useContext(PreferencesContext); 36 | const isScale = state.resourcesType == APP_DATA_TYPES.SCALE; 37 | 38 | return ( 39 | 40 | } 44 | keyExtractor={(item) => item.id.toString()} 45 | // eslint-disable-next-line react-native/no-inline-styles 46 | style={{ 47 | height: '100%', 48 | backgroundColor: DARKMODE ? colors.black : colors.systemGray6Light, 49 | }} 50 | /> 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/Screens/ScaleDetail/ScaleDetail.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import ScaleDetail from './ScaleDetail'; 4 | import MockContext from '../../../jest/MockContext'; 5 | 6 | import { render } from '@testing-library/react-native'; 7 | 8 | test('ScaleDetail renders correctly', () => { 9 | render( 10 | 11 | 23 | , 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /src/Screens/Statistics/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Statistics'; 2 | -------------------------------------------------------------------------------- /src/Screens/Statistics/utils/formatStatisticsDataForList/index.tsx: -------------------------------------------------------------------------------- 1 | import { translate } from '../../../../Translations/TranslationModel'; 2 | 3 | /** 4 | * @function formatStatisticsDataForList 5 | * @memberof Statistics 6 | * @description Formats the data from the style it is saved in the phone 7 | * to a style that can be displayed on the Statistics Section List. 8 | * Created 9/4/23 by Alexander Burdiss 9 | * @param {Object} data One of the statistics data, either Scales or values. 10 | * @returns {Object[]} An array of data to pass to a section list section. 11 | * 12 | * @copyright 2025 Alexander Burdiss 13 | * @author Alexander Burdiss 14 | * @since 2/1/25 15 | * @version 1.0.0 16 | * @example 17 | * formatStatisticsDataForList(statistics.scales) 18 | */ 19 | export function formatStatisticsDataForList(data: { [key: string]: number }) { 20 | if (Object.keys(data).length === 0) { 21 | return [{ id: '0', value: translate('Nothing Practiced Yet!') }]; 22 | } 23 | 24 | return Object.keys(data).map((item) => { 25 | const value = data[item]; 26 | return { 27 | id: item, 28 | value: item + ': ' + value, 29 | }; 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/Translations/TranslationModel.ts: -------------------------------------------------------------------------------- 1 | import * as RNLocalize from 'react-native-localize'; 2 | import i18n from 'i18n-js'; 3 | 4 | const translationGetters: { [key: string]: Function } = { 5 | en: () => require('./en.json'), 6 | zh: () => require('./zh.json'), 7 | fr: () => require('./fr.json'), 8 | ja: () => require('./ja.json'), 9 | ko: () => require('./ko.json'), 10 | es: () => require('./es.json'), 11 | }; 12 | 13 | const translationMemo: { [key: string]: string } = {}; 14 | 15 | /** 16 | * @function translate 17 | * @description Takes a string, and returns the translated version of that 18 | * string, if it exists in the configuration file for the language provided. 19 | * @param {string} key The string to be translated 20 | * @returns {string} The input string translated into the language the device 21 | * is currently in. 22 | * 23 | * @copyright 2025 Alexander Burdiss 24 | * @author Alexander Burdiss 25 | * @since 1/18/25 26 | * @version 2.0.0 27 | */ 28 | export function translate(key?: string): string { 29 | if (!key) { 30 | return ''; 31 | } 32 | const savedTranslation = translationMemo[key]; 33 | if (savedTranslation) { 34 | return savedTranslation; 35 | } 36 | const translation = i18n.t(key); 37 | translationMemo[key] = translation; 38 | return translation; 39 | } 40 | 41 | /** 42 | * @function setI18nConfig 43 | * @description Finds the current language the device is in, updates the 44 | * language in state, and clears the translation cache. This should be called 45 | * once before the content in App.js loads. 46 | * Created 12/1/2020 by Alexander Burdiss 47 | * 48 | * @copyright 2025 Alexander Burdiss 49 | * @author Alexander Burdiss 50 | * @since 2/1/25 51 | * @version 1.0.1 52 | */ 53 | export function setI18nConfig() { 54 | const fallback = { languageTag: 'en' }; 55 | const { languageTag } = 56 | RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) || 57 | fallback; 58 | 59 | // Clear out memoized translations 60 | for (var variableKey in translationMemo) { 61 | if (Object.prototype.hasOwnProperty.call(translationMemo, variableKey)) { 62 | delete translationMemo[variableKey]; 63 | } 64 | } 65 | 66 | i18n.translations = { [languageTag]: translationGetters[languageTag]() }; 67 | i18n.locale = languageTag; 68 | } 69 | -------------------------------------------------------------------------------- /src/Translations/Translations.test.ts: -------------------------------------------------------------------------------- 1 | import englishTranslations from './en.json'; 2 | import frenchTranslations from './fr.json'; 3 | import spanishTranslations from './es.json'; 4 | import chineseTranslations from './zh.json'; 5 | import japaneseTranslations from './ja.json'; 6 | import koreanTranslations from './ko.json'; 7 | 8 | let englishTranslationList = Object.keys(englishTranslations); 9 | 10 | test.each(englishTranslationList)('french translations all exist', (item) => { 11 | expect(frenchTranslations).toHaveProperty(item); 12 | }); 13 | 14 | test.each(englishTranslationList)('spanish translations all exist', (item) => { 15 | expect(spanishTranslations).toHaveProperty(item); 16 | }); 17 | 18 | test.each(englishTranslationList)('chinese translations all exist', (item) => { 19 | expect(chineseTranslations).toHaveProperty(item); 20 | }); 21 | 22 | test.each(englishTranslationList)('korean translations all exist', (item) => { 23 | expect(koreanTranslations).toHaveProperty(item); 24 | }); 25 | 26 | test.each(englishTranslationList)('japanese translations all exist', (item) => { 27 | expect(japaneseTranslations).toHaveProperty(item); 28 | }); 29 | -------------------------------------------------------------------------------- /src/enums/appDataTypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum 3 | * @name APP_DATA_TYPES 4 | * @description The different Data types available throughout the application. 5 | * Created by Alexander Burdiss on 1/31/25 6 | * @copyright 2025 Alexander Burdiss 7 | * @author Alexander Burdiss 8 | * @since 1/31/25 9 | * @version 1.0.0 10 | */ 11 | export enum APP_DATA_TYPES { 12 | SCALE, 13 | ARPEGGIO, 14 | } 15 | -------------------------------------------------------------------------------- /src/enums/storageKeys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum {string} 3 | * @name STORAGE_KEYS 4 | * @description The different keys used to store data in the device storage. 5 | * Created 12/31/24 by Alexander Burdiss 6 | * 7 | * @copyright 2024 Alexander Burdiss 8 | * @since 12/31/24 9 | * @version 1.0.0 10 | */ 11 | export enum STORAGE_KEYS { 12 | preferences = 'preferences', 13 | random = 'random', 14 | advanced = 'advanced', 15 | statistics = 'statistics', 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/capitalize/capitalize.test.ts: -------------------------------------------------------------------------------- 1 | import { capitalize } from './capitalize'; 2 | 3 | test('utility is a function', () => { 4 | expect(typeof capitalize).toEqual('function'); 5 | }); 6 | 7 | describe('capitalize works correctly', () => { 8 | test('capitalizes first letter of string', () => { 9 | const input = 'hello, world'; 10 | const output = capitalize(input); 11 | const expected = 'Hello, world'; 12 | expect(output).toEqual(expected); 13 | }); 14 | 15 | test('leaves capitalization if already capitalized', () => { 16 | const input = 'Hello, world'; 17 | const output = capitalize(input); 18 | expect(output).toEqual(input); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/utils/capitalize/capitalize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @function capitalize 3 | * @description Capitalizes the first letter of the string passed in. 4 | * Created 9/11/21 by Alexander Burdiss 5 | * @param {string} inputString A string to have the first letter capitalized in 6 | * @returns {string} The exact same string that was passed in, but with the 7 | * first letter capitalized. 8 | * 9 | * @copyright 2025 Alexander Burdiss 10 | * @author Alexander Burdiss 11 | * @since 2/1/25 12 | * @version 1.0.1 13 | */ 14 | export function capitalize(inputString: string): string { 15 | const firstLetter = inputString[0]; 16 | const restOfString = inputString.slice(1); 17 | return `${firstLetter.toUpperCase()}${restOfString}`; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/createArpeggioArrayFromParts/index.ts: -------------------------------------------------------------------------------- 1 | import { translate } from '../../Translations/TranslationModel'; 2 | import { shuffle } from '..'; 3 | import { indeterminantLetterNames } from '../../Model/Model'; 4 | 5 | /** 6 | * @function createArpeggioArrayFromParts 7 | * @description Constructs the arpeggio name and arpeggio note together to 8 | * form one string to display on the screen. 9 | * Created 10/12/20 by Alexander Burdiss 10 | * @param {string} arpeggioName The name of the arpeggio to create an array of 11 | * all possible arpeggios for. 12 | * @returns {string[]} An array with all 12 keys of arpeggio that was passed in. 13 | * 14 | * @copyright 2025 Alexander Burdiss 15 | * @author Alexander Burdiss 16 | * @since 2/1/25 17 | * @version 1.0.3 18 | */ 19 | export function createArpeggioArrayFromParts(arpeggioName: string): string[] { 20 | let allLetterNamesOfArpeggio = []; 21 | for (let letter of indeterminantLetterNames) { 22 | allLetterNamesOfArpeggio.push(`${letter} ${translate(arpeggioName)}`); 23 | } 24 | allLetterNamesOfArpeggio = shuffle(allLetterNamesOfArpeggio); 25 | return allLetterNamesOfArpeggio; 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/createScaleArrayFromParts/index.ts: -------------------------------------------------------------------------------- 1 | import { translate } from '../../Translations/TranslationModel'; 2 | import { shuffle } from '..'; 3 | 4 | /** 5 | * @function createScaleArrayFromParts 6 | * @description Constructs the scale name and scale note together to form one 7 | * string to display on the screen. 8 | * Created 10/12/20 by Alexander Burdiss 9 | * @param {string[]} letterNames All possible note letter names 10 | * @param {string[]} scaleNames All possible scale names 11 | * @returns {string[]} array of all transpositions of a scale 12 | * 13 | * @copyright 2025 Alexander Burdiss 14 | * @author Alexander Burdiss 15 | * @since 2/1/25 16 | * @version 2.0.1 17 | */ 18 | export function createScaleArrayFromParts( 19 | letterNames: string[], 20 | scaleNames: string[], 21 | ): string[] { 22 | let allLetterNamesOfScale = []; 23 | for (let letter of letterNames) { 24 | for (let scaleName of scaleNames) { 25 | allLetterNamesOfScale.push(`${letter} ${translate(scaleName)}`); 26 | } 27 | } 28 | allLetterNamesOfScale = shuffle(allLetterNamesOfScale); 29 | return allLetterNamesOfScale; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/getIsSmallScreen/getIsSmallScreen.ts: -------------------------------------------------------------------------------- 1 | import { Dimensions } from 'react-native'; 2 | 3 | /** 4 | * @function getIsSmallScreen 5 | * @description Checks whether the screen is small by checking it's longest 6 | * edge's width. 7 | * Created 1/28/21 by Alexander Burdiss 8 | * @returns {boolean} A boolean of whether or not the screen is small. 9 | * 10 | * @copyright 2025 Alexander Burdiss 11 | * @author Alexander Burdiss 12 | * @since 2/1/25 13 | * @version 1.0.1 14 | */ 15 | export function getIsSmallScreen(): boolean { 16 | const SMALL_SCREEN_HEIGHT = 675; 17 | return Dimensions.get('screen').height < SMALL_SCREEN_HEIGHT; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/getTabBarIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Ionicons from 'react-native-vector-icons/Ionicons'; 4 | 5 | /** 6 | * @function getTabBarIcon 7 | * @description A function that gets the Tab Bar Icon from the passed in route 8 | * object. 9 | * @param {Object} route The route object to get the correct tab bar Icon from. 10 | * @returns {React.Component} A react component ready to be rendered. 11 | * 12 | * @copyright 2025 Alexander Burdiss 13 | * @since 2/1/25 14 | * @version 1.0.0 15 | * @example 16 | * function Component() { 17 | * const Icon = getTabBarIcon(route); 18 | * return ( 19 | * 20 | * 21 | * 22 | * ) 23 | * } 24 | */ 25 | export function getTabBarIcon(route: { name: string }) { 26 | function Icon({ color, size }: { color: string; size: number }) { 27 | let iconName: string = ''; 28 | if (route.name === 'RandomStack') { 29 | iconName = 'cube'; 30 | } else if (route.name === 'ResourcesStack') { 31 | iconName = 'book'; 32 | } else if (route.name === 'AdvancedStack') { 33 | iconName = 'create'; 34 | } else if (route.name === 'MoreStack') { 35 | iconName = 'ellipsis-horizontal-circle-sharp'; 36 | } 37 | return ; 38 | } 39 | Icon.propTypes = { 40 | color: PropTypes.string, 41 | size: PropTypes.string, 42 | }; 43 | return Icon; 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './capitalize/capitalize'; 2 | export * from './createArpeggioArrayFromParts'; 3 | export * from './createScaleArrayFromParts'; 4 | export * from './getIsSmallScreen/getIsSmallScreen'; 5 | export * from './getTabBarIcon'; 6 | export * from './random/random'; 7 | export * from './shuffle/shuffle'; 8 | export * from './useDarkMode'; 9 | export * from './useIdleScreen/useIdleScreen'; 10 | -------------------------------------------------------------------------------- /src/utils/loadFromStorage/index.ts: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | import { STORAGE_KEYS } from '../../enums/storageKeys'; 3 | 4 | /** 5 | * @function loadFromStorage 6 | * @description Loads data based on passed in key from local storage 7 | * Created 12/28/24 by Alexander Burdiss 8 | * @param {string} type Type of data to load. 9 | * @returns {JSON|null} The stored value or null, depending on if the data is 10 | * successfully retrieved. 11 | * 12 | * @copyright 2025 Alexander Burdiss 13 | * @author Alexander Burdiss 14 | * @since 1/31/25 15 | * @version 2.0.0 16 | */ 17 | export async function loadFromStorage(storageKey: STORAGE_KEYS) { 18 | try { 19 | const jsonValue = await AsyncStorage.getItem(storageKey); 20 | return jsonValue != null ? JSON.parse(jsonValue) : null; 21 | } catch (e) { 22 | console.log(e); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/random/random.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @function random 3 | * @description Returns a random number between the min and max 4 | * @param {number} min The minimum to find a random number between 5 | * @param {number} max The maximum to find a random number between 6 | * @returns {number} A random number between min and max 7 | * 8 | * @copyright 2025 Alexander Burdiss 9 | * @author Alexander Burdiss 10 | * @since 2/1/25 11 | * @version 2.0.0 12 | * @example const randomIndex = random(0, array.length); 13 | */ 14 | export function random(min: number, max: number) { 15 | return min + Math.floor(Math.random() * (max - min + 1)); 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/saveToStorage/index.ts: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | import { STORAGE_KEYS } from '../../enums/storageKeys'; 3 | 4 | /** 5 | * @function saveToStorage 6 | * @description Stores Random reducer Data in Local Storage 7 | * Created 12/28/24 by Alexander Burdiss 8 | * @param {string} type Type of data to store. 9 | * @param {Object} data Data to be stored in local storage 10 | * @copyright 2025 Alexander Burdiss 11 | * @author Alexander Burdiss 12 | * @since 1/31/25 13 | * @version 1.0.0 14 | */ 15 | export async function saveToStorage(storageKey: STORAGE_KEYS, data: Object) { 16 | try { 17 | const jsonValue = JSON.stringify(data); 18 | await AsyncStorage.setItem(storageKey, jsonValue); 19 | } catch (e) { 20 | console.log(e); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/shuffle/shuffle.ts: -------------------------------------------------------------------------------- 1 | import { random } from '../random/random'; 2 | 3 | /** 4 | * @function shuffle 5 | * @description Shuffles an array of anything passed in. 6 | * Created February 3, 2022 by Alexander Burdiss to replace underscore 7 | * dependency 8 | * @param {any[]} input An array of anything (and any length) 9 | * @returns {any[]} The same array, but shuffled. 10 | * 11 | * @copyright 2025 Alexander Burdiss 12 | * @author Alexander Burdiss 13 | * @since 2/1/2025 14 | * @version 1.1.0 15 | */ 16 | export function shuffle(input: any[]) { 17 | const array = [...input]; 18 | for (var i = array.length - 1; i > 0; i--) { 19 | // Generate random number 20 | var j = random(0, i + 1); 21 | 22 | var temp = array[i]; 23 | array[i] = array[j]; 24 | array[j] = temp; 25 | } 26 | 27 | return array; 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/useDarkMode/index.ts: -------------------------------------------------------------------------------- 1 | import { useColorScheme } from 'react-native'; 2 | 3 | /** 4 | * @function useDarkMode 5 | * @description A React Hook used to determine if the device is in dark mode 6 | * or not 7 | * Created 10/25/22 by Alexander Burdiss 8 | * @returns {boolean} Whether or not the device is in Dark Mode 9 | * 10 | * @copyright 2025 Alexander Burdiss 11 | * @author Alexander Burdiss 12 | * @since 2/1/25 13 | * @version 1.0.1 14 | */ 15 | export function useDarkMode(): boolean { 16 | const colorTheme = useColorScheme(); 17 | return colorTheme === 'dark'; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/useIdleScreen/useIdleScreen.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useContext } from 'react'; 2 | import IdleTimerManager from 'react-native-idle-timer'; 3 | 4 | import { PreferencesContext } from '../../Model/Preferences'; 5 | 6 | /** 7 | * @function useIdleScreen 8 | * @description Turns the screen timer off or on, depending on what the user 9 | * has selected in preferences. If the user has no preference set, this will 10 | * default to false, not adjusting the screen timer settings. 11 | * Created by Alexander Burdiss 7/6/21 12 | * 13 | * @copyright 2025 Alexander Burdiss 14 | * @author Alexander Burdiss 15 | * @since 2/1/25 16 | * @version 1.0.1 17 | * @example 18 | * function Component() { 19 | * useIdleScreen(); 20 | * return ; 21 | * } 22 | */ 23 | export function useIdleScreen() { 24 | const { state } = useContext(PreferencesContext); 25 | 26 | useEffect( 27 | function setupIdleScreenPreferences() { 28 | if (state?.disableScreenSleep) { 29 | IdleTimerManager.setIdleTimerDisabled(true); 30 | } else { 31 | IdleTimerManager.setIdleTimerDisabled(false); 32 | } 33 | 34 | return () => { 35 | IdleTimerManager.setIdleTimerDisabled(false); 36 | }; 37 | }, 38 | [state?.disableScreenSleep], 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "noImplicitThis": false, 6 | "moduleSuffixes": [".ios", ".android", ".native", ""], 7 | "paths": { 8 | "react": ["./node_modules/@types/react"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "paths": { 6 | "react": ["./node_modules/@types/react"] 7 | } 8 | } 9 | } 10 | --------------------------------------------------------------------------------