├── .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 |
2 |
3 | # Scale Practice
4 |
5 |
6 |
7 |
8 |
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 |
--------------------------------------------------------------------------------