├── .github └── workflows │ ├── pre-commit.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .swiftformat ├── DEVELOPING.md ├── Fingerspelling.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── Fingerspelling.xcscheme │ │ └── FingerspellingUITests.xcscheme └── xcuserdata │ └── sloria.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Fingerspelling ├── AboutView.swift ├── AppDelegate.swift ├── AppView.swift ├── Assets.xcassets │ ├── A-lauren-nobg.imageset │ │ ├── A-lauren-nobg.png │ │ ├── A-lauren-nobg@2x.png │ │ ├── A-lauren-nobg@3x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── ios_marketing1024x1024.png │ │ ├── ipad_app76x76.png │ │ ├── ipad_app76x76@2x.png │ │ ├── ipad_notification20x20.png │ │ ├── ipad_notification20x20@2x.png │ │ ├── ipad_pro_app83.5x83.5@2x.png │ │ ├── ipad_settings29x29.png │ │ ├── ipad_settings29x29@2x.png │ │ ├── ipad_spotlight40x40.png │ │ ├── ipad_spotlight40x40@2x.png │ │ ├── iphone_app60x60@2x.png │ │ ├── iphone_app60x60@3x.png │ │ ├── iphone_notification20x20@2x.png │ │ ├── iphone_notification20x20@3x.png │ │ ├── iphone_settings29x29@2x.png │ │ ├── iphone_settings29x29@3x.png │ │ ├── iphone_spotlight40x40@2x.png │ │ └── iphone_spotlight40x40@3x.png │ ├── B-lauren-nobg.imageset │ │ ├── B-lauren-nobg.png │ │ ├── B-lauren-nobg@2x.png │ │ ├── B-lauren-nobg@3x.png │ │ └── Contents.json │ ├── C-lauren-nobg.imageset │ │ ├── C-lauren-nobg.png │ │ ├── C-lauren-nobg@2x.png │ │ ├── C-lauren-nobg@3x.png │ │ └── Contents.json │ ├── Contents.json │ ├── D-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── D-lauren-nobg.png │ │ ├── D-lauren-nobg@2x.png │ │ └── D-lauren-nobg@3x.png │ ├── E-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── E-lauren-nobg.png │ │ ├── E-lauren-nobg@2x.png │ │ └── E-lauren-nobg@3x.png │ ├── F-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── F-lauren-nobg.png │ │ ├── F-lauren-nobg@2x.png │ │ └── F-lauren-nobg@3x.png │ ├── G-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── G-lauren-nobg.png │ │ ├── G-lauren-nobg@2x.png │ │ └── G-lauren-nobg@3x.png │ ├── H-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── H-lauren-nobg.png │ │ ├── H-lauren-nobg@2x.png │ │ └── H-lauren-nobg@3x.png │ ├── I-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── I-lauren-nobg.png │ │ ├── I-lauren-nobg@2x.png │ │ └── I-lauren-nobg@3x.png │ ├── J-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── J-lauren-nobg.png │ │ ├── J-lauren-nobg@2x.png │ │ └── J-lauren-nobg@3x.png │ ├── K-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── K-lauren-nobg.png │ │ ├── K-lauren-nobg@2x.png │ │ └── K-lauren-nobg@3x.png │ ├── L-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── L-lauren-nobg.png │ │ ├── L-lauren-nobg@2x.png │ │ └── L-lauren-nobg@3x.png │ ├── M-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── M-lauren-nobg.png │ │ ├── M-lauren-nobg@2x.png │ │ └── M-lauren-nobg@3x.png │ ├── N-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── N-lauren-nobg.png │ │ ├── N-lauren-nobg@2x.png │ │ └── N-lauren-nobg@3x.png │ ├── O-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── O-lauren-nobg.png │ │ ├── O-lauren-nobg@2x.png │ │ └── O-lauren-nobg@3x.png │ ├── P-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── P-lauren-nobg.png │ │ ├── P-lauren-nobg@2x.png │ │ └── P-lauren-nobg@3x.png │ ├── Q-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── Q-lauren-nobg.png │ │ ├── Q-lauren-nobg@2x.png │ │ └── Q-lauren-nobg@3x.png │ ├── R-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── R-lauren-nobg.png │ │ ├── R-lauren-nobg@2x.png │ │ └── R-lauren-nobg@3x.png │ ├── S-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── S-lauren-nobg.png │ │ ├── S-lauren-nobg@2x.png │ │ └── S-lauren-nobg@3x.png │ ├── T-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── T-lauren-nobg.png │ │ ├── T-lauren-nobg@2x.png │ │ └── T-lauren-nobg@3x.png │ ├── U-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── U-lauren-nobg.png │ │ ├── U-lauren-nobg@2x.png │ │ └── U-lauren-nobg@3x.png │ ├── V-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── V-lauren-nobg.png │ │ ├── V-lauren-nobg@2x.png │ │ └── V-lauren-nobg@3x.png │ ├── W-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── W-lauren-nobg.png │ │ ├── W-lauren-nobg@2x.png │ │ └── W-lauren-nobg@3x.png │ ├── X-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── X-lauren-nobg.png │ │ ├── X-lauren-nobg@2x.png │ │ └── X-lauren-nobg@3x.png │ ├── Y-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── Y-lauren-nobg.png │ │ ├── Y-lauren-nobg@2x.png │ │ └── Y-lauren-nobg@3x.png │ └── Z-lauren-nobg.imageset │ │ ├── Contents.json │ │ ├── Z-lauren-nobg.png │ │ ├── Z-lauren-nobg@2x.png │ │ └── Z-lauren-nobg@3x.png ├── CheckmarkAnimationView.swift ├── CurrentWordDisplayView.swift ├── Data │ └── Words.swift ├── ExpressiveGameView.swift ├── ExpressiveStatsView.swift ├── FeedbackView.swift ├── GameModeIconView.swift ├── Info.plist ├── NavbarView.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── ReceptiveGameView.swift ├── ReceptiveStatsView.swift ├── SceneDelegate.swift ├── ScoreIndicatorView.swift ├── SettingsView.swift ├── SideMenuView.swift ├── StatItemView.swift ├── appState.swift ├── util.swift └── viewModifiers.swift ├── FingerspellingUITests ├── FingerspellingUITests.swift └── Info.plist ├── Gemfile ├── Gemfile.lock ├── PRIVACY.md ├── README.md ├── SUPPORT.md ├── fastlane ├── Appfile ├── Fastfile ├── README.md ├── Snapfile └── SnapshotHelper.swift ├── media └── screenshot.png └── scripts ├── README.md ├── blacklist.db ├── bump.sh ├── gen_words.py ├── gen_words.sh ├── screenshots.sh └── test.sh /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | pre-commit: 7 | runs-on: macos-latest 8 | steps: 9 | - uses: actions/checkout@master 10 | - uses: actions/setup-python@v1 11 | - name: set PY 12 | run: echo "::set-env name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" 13 | - name: Install swiftformat 14 | run: brew install swiftformat 15 | - uses: actions/cache@v1 16 | with: 17 | path: ~/.cache/pre-commit 18 | key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} 19 | - uses: pre-commit/action@v1.0.1 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | jobs: 3 | test: 4 | runs-on: macOS-latest 5 | strategy: 6 | matrix: 7 | destination: ['platform=iOS Simulator,OS=13.3,name=iPhone 11'] 8 | steps: 9 | - uses: actions/checkout@master 10 | - name: Run tests 11 | run: ./scripts/test.sh "${destination}" 12 | env: 13 | destination: ${{ matrix.destination }} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ########## Generated by gig ########### 2 | 3 | ### Swift ### 4 | 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## User settings 10 | xcuserdata/ 11 | 12 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 13 | *.xcscmblueprint 14 | *.xccheckout 15 | 16 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 17 | build/ 18 | DerivedData/ 19 | *.moved-aside 20 | *.pbxuser 21 | !default.pbxuser 22 | *.mode1v3 23 | !default.mode1v3 24 | *.mode2v3 25 | !default.mode2v3 26 | *.perspectivev3 27 | !default.perspectivev3 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | 32 | ## App packaging 33 | *.ipa 34 | *.dSYM.zip 35 | *.dSYM 36 | 37 | ## Playgrounds 38 | timeline.xctimeline 39 | playground.xcworkspace 40 | 41 | # Swift Package Manager 42 | # 43 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 44 | # Packages/ 45 | # Package.pins 46 | # Package.resolved 47 | # *.xcodeproj 48 | # 49 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 50 | # hence it is not needed unless you have added a package configuration file to your project 51 | # .swiftpm 52 | 53 | .build/ 54 | 55 | # CocoaPods 56 | # 57 | # We recommend against adding the Pods directory to your .gitignore. However 58 | # you should judge for yourself, the pros and cons are mentioned at: 59 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 60 | # 61 | # Pods/ 62 | # 63 | # Add this line if you want to avoid checking in source code from the Xcode workspace 64 | # *.xcworkspace 65 | 66 | # Carthage 67 | # 68 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 69 | # Carthage/Checkouts 70 | 71 | Carthage/Build/ 72 | 73 | # Accio dependency management 74 | Dependencies/ 75 | .accio/ 76 | 77 | # fastlane 78 | # 79 | # It is recommended to not store the screenshots in the git repo. 80 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 81 | # For more information about the recommended setup visit: 82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 83 | 84 | fastlane/report.xml 85 | fastlane/Preview.html 86 | fastlane/screenshots/**/*.png 87 | fastlane/test_output 88 | 89 | # Code Injection 90 | # 91 | # After new code Injection tools there's a generated folder /iOSInjectionProject 92 | # https://github.com/johnno1962/injectionforxcode 93 | 94 | iOSInjectionProject/ 95 | 96 | # fastlane specific 97 | **/fastlane/report.xml 98 | 99 | # deliver temporary files 100 | **/fastlane/Preview.html 101 | 102 | # snapshot generated screenshots 103 | **/fastlane/screenshots 104 | 105 | # scan temporary files 106 | **/fastlane/test_output 107 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/nicklockwood/SwiftFormat 3 | rev: 0.44.4 4 | hooks: 5 | - id: swiftformat 6 | exclude: ^Fingerspelling/Data/Words.swift 7 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --swiftversion 5.1 2 | --self insert 3 | --indent 2 4 | --header strip 5 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | ## Running tests 4 | 5 | From the command line: 6 | 7 | ``` 8 | ./scripts/tests.sh 9 | ``` 10 | 11 | Or press Cmd+U in Xcode. 12 | 13 | ## Adding pre-commit hooks 14 | 15 | Ensure pre-commit is installed: 16 | 17 | ``` 18 | brew install pre-commit 19 | ``` 20 | 21 | Install the hooks: 22 | 23 | ``` 24 | pre-commit install 25 | ``` 26 | 27 | To manually run formatters on all files: 28 | 29 | ``` 30 | pre-commit run --all-files 31 | ``` 32 | 33 | ## Generating screenshots 34 | 35 | Install fastlane: 36 | 37 | ``` 38 | brew install fastlane 39 | ``` 40 | 41 | Run the screenshots script: 42 | 43 | ``` 44 | ./scripts/screenshots.sh 45 | ``` 46 | 47 | ## Regenerating words list 48 | 49 | ``` 50 | ./scripts/gen_words.sh 51 | ``` 52 | 53 | ## Bumping version and build number 54 | 55 | Replace `X` with release number. 56 | 57 | ``` 58 | ./scripts/bump.sh 2020.X 59 | ``` 60 | 61 | ## Releasing 62 | 63 | 1. Bump version: `./scripts/bump.sh 2020.X`. 64 | 1. Push: `git push --tags origin master` 65 | 1. In Xcode, choose the "Fingerspelling" scheme and "Generic iOS Device" as the device. 66 | 1. Click Product > Archive and wait for the build to finish (this takes a while). 67 | 1. Click "Distribute app". Hit Next through the following Menus. 68 | 1. Add a new version on [App Store Connect](https://appstoreconnect.apple.com/). If necessary, regenerate snapshots (see above) and upload them. 69 | 1. Submit the new version. 70 | -------------------------------------------------------------------------------- /Fingerspelling.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4315BFF12427F9CC002E2C38 /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4315BFF02427F9CC002E2C38 /* FeedbackView.swift */; }; 11 | 4317BBB8242FB99D00A2093D /* CheckmarkAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4317BBB7242FB99D00A2093D /* CheckmarkAnimationView.swift */; }; 12 | 4321ECC32421812B00DBA702 /* FingerspellingUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4321ECC22421812B00DBA702 /* FingerspellingUITests.swift */; }; 13 | 4321ECCD2421A56200DBA702 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4321ECCC2421A56200DBA702 /* SnapshotHelper.swift */; }; 14 | 43422CA32421DED30026D68D /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43422CA22421DED30026D68D /* AboutView.swift */; }; 15 | 4365109524246A0700EB8937 /* ReceptiveStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4365109424246A0700EB8937 /* ReceptiveStatsView.swift */; }; 16 | 4365109724246A4D00EB8937 /* ExpressiveStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4365109624246A4D00EB8937 /* ExpressiveStatsView.swift */; }; 17 | 438EEEBE2427D58600F74F9A /* StatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438EEEBD2427D58600F74F9A /* StatItemView.swift */; }; 18 | 43BCEB3A2421053A0033A56B /* ReceptiveGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB392421053A0033A56B /* ReceptiveGameView.swift */; }; 19 | 43BCEB3C24210AB40033A56B /* ExpressiveGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB3B24210AB40033A56B /* ExpressiveGameView.swift */; }; 20 | 43BCEB3E24210E4C0033A56B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB3D24210E4C0033A56B /* SettingsView.swift */; }; 21 | 43BCEB4024210EEC0033A56B /* NavbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB3F24210EEC0033A56B /* NavbarView.swift */; }; 22 | 43BCEB422421108B0033A56B /* CurrentWordDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB412421108B0033A56B /* CurrentWordDisplayView.swift */; }; 23 | 43BCEB44242111AE0033A56B /* ScoreIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB43242111AE0033A56B /* ScoreIndicatorView.swift */; }; 24 | 43BCEB462421127D0033A56B /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB452421127D0033A56B /* SideMenuView.swift */; }; 25 | 43BCEB48242114040033A56B /* GameModeIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB47242114040033A56B /* GameModeIconView.swift */; }; 26 | 43BCEB4A2421151E0033A56B /* appState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB492421151E0033A56B /* appState.swift */; }; 27 | 43BCEB4C242115CA0033A56B /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB4B242115CA0033A56B /* util.swift */; }; 28 | 43BCEB4E242116810033A56B /* viewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB4D242116810033A56B /* viewModifiers.swift */; }; 29 | 43BCEB51242126900033A56B /* Words.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BCEB50242126900033A56B /* Words.swift */; }; 30 | 43D610812413D8640089DE4D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D610802413D8640089DE4D /* AppDelegate.swift */; }; 31 | 43D610832413D8640089DE4D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D610822413D8640089DE4D /* SceneDelegate.swift */; }; 32 | 43D610852413D8640089DE4D /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D610842413D8640089DE4D /* AppView.swift */; }; 33 | 43D610872413D8680089DE4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43D610862413D8680089DE4D /* Assets.xcassets */; }; 34 | 43D6108A2413D8680089DE4D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43D610892413D8680089DE4D /* Preview Assets.xcassets */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | 4321ECC52421812B00DBA702 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 43D610752413D8630089DE4D /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 43D6107C2413D8630089DE4D; 43 | remoteInfo = Fingerspelling; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 4315BFF02427F9CC002E2C38 /* FeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = ""; }; 49 | 4317BBB7242FB99D00A2093D /* CheckmarkAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkAnimationView.swift; sourceTree = ""; }; 50 | 4321ECC02421812B00DBA702 /* FingerspellingUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FingerspellingUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 4321ECC22421812B00DBA702 /* FingerspellingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerspellingUITests.swift; sourceTree = ""; }; 52 | 4321ECC42421812B00DBA702 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 4321ECCC2421A56200DBA702 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 54 | 43422CA22421DED30026D68D /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 55 | 4365109424246A0700EB8937 /* ReceptiveStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceptiveStatsView.swift; sourceTree = ""; }; 56 | 4365109624246A4D00EB8937 /* ExpressiveStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpressiveStatsView.swift; sourceTree = ""; }; 57 | 438EEEBD2427D58600F74F9A /* StatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatItemView.swift; sourceTree = ""; }; 58 | 43BCEB392421053A0033A56B /* ReceptiveGameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceptiveGameView.swift; sourceTree = ""; }; 59 | 43BCEB3B24210AB40033A56B /* ExpressiveGameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpressiveGameView.swift; sourceTree = ""; }; 60 | 43BCEB3D24210E4C0033A56B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 61 | 43BCEB3F24210EEC0033A56B /* NavbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavbarView.swift; sourceTree = ""; }; 62 | 43BCEB412421108B0033A56B /* CurrentWordDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWordDisplayView.swift; sourceTree = ""; }; 63 | 43BCEB43242111AE0033A56B /* ScoreIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoreIndicatorView.swift; sourceTree = ""; }; 64 | 43BCEB452421127D0033A56B /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = ""; }; 65 | 43BCEB47242114040033A56B /* GameModeIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameModeIconView.swift; sourceTree = ""; }; 66 | 43BCEB492421151E0033A56B /* appState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = appState.swift; sourceTree = ""; }; 67 | 43BCEB4B242115CA0033A56B /* util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = util.swift; sourceTree = ""; }; 68 | 43BCEB4D242116810033A56B /* viewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = viewModifiers.swift; sourceTree = ""; }; 69 | 43BCEB50242126900033A56B /* Words.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Words.swift; sourceTree = ""; }; 70 | 43D6107D2413D8640089DE4D /* Fingerspelling.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Fingerspelling.app; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 43D610802413D8640089DE4D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 72 | 43D610822413D8640089DE4D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 73 | 43D610842413D8640089DE4D /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; 74 | 43D610862413D8680089DE4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 75 | 43D610892413D8680089DE4D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 76 | 43D6108E2413D8680089DE4D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | /* End PBXFileReference section */ 78 | 79 | /* Begin PBXFrameworksBuildPhase section */ 80 | 4321ECBD2421812B00DBA702 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | 43D6107A2413D8630089DE4D /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | 4321ECC12421812B00DBA702 /* FingerspellingUITests */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 4321ECCC2421A56200DBA702 /* SnapshotHelper.swift */, 101 | 4321ECC22421812B00DBA702 /* FingerspellingUITests.swift */, 102 | 4321ECC42421812B00DBA702 /* Info.plist */, 103 | ); 104 | path = FingerspellingUITests; 105 | sourceTree = ""; 106 | }; 107 | 43BCEB4F24211E9D0033A56B /* Data */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 43BCEB50242126900033A56B /* Words.swift */, 111 | ); 112 | path = Data; 113 | sourceTree = ""; 114 | }; 115 | 43D610742413D8630089DE4D = { 116 | isa = PBXGroup; 117 | children = ( 118 | 43D6107F2413D8640089DE4D /* Fingerspelling */, 119 | 4321ECC12421812B00DBA702 /* FingerspellingUITests */, 120 | 43D6107E2413D8640089DE4D /* Products */, 121 | ); 122 | sourceTree = ""; 123 | }; 124 | 43D6107E2413D8640089DE4D /* Products */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 43D6107D2413D8640089DE4D /* Fingerspelling.app */, 128 | 4321ECC02421812B00DBA702 /* FingerspellingUITests.xctest */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 43D6107F2413D8640089DE4D /* Fingerspelling */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 43BCEB4F24211E9D0033A56B /* Data */, 137 | 43D610802413D8640089DE4D /* AppDelegate.swift */, 138 | 43D610822413D8640089DE4D /* SceneDelegate.swift */, 139 | 43D610842413D8640089DE4D /* AppView.swift */, 140 | 43D610862413D8680089DE4D /* Assets.xcassets */, 141 | 43D6108E2413D8680089DE4D /* Info.plist */, 142 | 43D610882413D8680089DE4D /* Preview Content */, 143 | 43BCEB392421053A0033A56B /* ReceptiveGameView.swift */, 144 | 43BCEB3B24210AB40033A56B /* ExpressiveGameView.swift */, 145 | 43BCEB3D24210E4C0033A56B /* SettingsView.swift */, 146 | 43BCEB3F24210EEC0033A56B /* NavbarView.swift */, 147 | 43BCEB412421108B0033A56B /* CurrentWordDisplayView.swift */, 148 | 43BCEB43242111AE0033A56B /* ScoreIndicatorView.swift */, 149 | 43BCEB452421127D0033A56B /* SideMenuView.swift */, 150 | 43BCEB47242114040033A56B /* GameModeIconView.swift */, 151 | 43BCEB492421151E0033A56B /* appState.swift */, 152 | 43BCEB4B242115CA0033A56B /* util.swift */, 153 | 43BCEB4D242116810033A56B /* viewModifiers.swift */, 154 | 43422CA22421DED30026D68D /* AboutView.swift */, 155 | 4365109424246A0700EB8937 /* ReceptiveStatsView.swift */, 156 | 4365109624246A4D00EB8937 /* ExpressiveStatsView.swift */, 157 | 438EEEBD2427D58600F74F9A /* StatItemView.swift */, 158 | 4315BFF02427F9CC002E2C38 /* FeedbackView.swift */, 159 | 4317BBB7242FB99D00A2093D /* CheckmarkAnimationView.swift */, 160 | ); 161 | path = Fingerspelling; 162 | sourceTree = ""; 163 | }; 164 | 43D610882413D8680089DE4D /* Preview Content */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 43D610892413D8680089DE4D /* Preview Assets.xcassets */, 168 | ); 169 | path = "Preview Content"; 170 | sourceTree = ""; 171 | }; 172 | /* End PBXGroup section */ 173 | 174 | /* Begin PBXNativeTarget section */ 175 | 4321ECBF2421812B00DBA702 /* FingerspellingUITests */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = 4321ECC92421812B00DBA702 /* Build configuration list for PBXNativeTarget "FingerspellingUITests" */; 178 | buildPhases = ( 179 | 4321ECBC2421812B00DBA702 /* Sources */, 180 | 4321ECBD2421812B00DBA702 /* Frameworks */, 181 | 4321ECBE2421812B00DBA702 /* Resources */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | 4321ECC62421812B00DBA702 /* PBXTargetDependency */, 187 | ); 188 | name = FingerspellingUITests; 189 | productName = FingerspellingUITests; 190 | productReference = 4321ECC02421812B00DBA702 /* FingerspellingUITests.xctest */; 191 | productType = "com.apple.product-type.bundle.ui-testing"; 192 | }; 193 | 43D6107C2413D8630089DE4D /* Fingerspelling */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = 43D6109C2413D8690089DE4D /* Build configuration list for PBXNativeTarget "Fingerspelling" */; 196 | buildPhases = ( 197 | 43D610792413D8630089DE4D /* Sources */, 198 | 43D6107A2413D8630089DE4D /* Frameworks */, 199 | 43D6107B2413D8630089DE4D /* Resources */, 200 | ); 201 | buildRules = ( 202 | ); 203 | dependencies = ( 204 | ); 205 | name = Fingerspelling; 206 | productName = Fingerspelling; 207 | productReference = 43D6107D2413D8640089DE4D /* Fingerspelling.app */; 208 | productType = "com.apple.product-type.application"; 209 | }; 210 | /* End PBXNativeTarget section */ 211 | 212 | /* Begin PBXProject section */ 213 | 43D610752413D8630089DE4D /* Project object */ = { 214 | isa = PBXProject; 215 | attributes = { 216 | LastSwiftUpdateCheck = 1130; 217 | LastUpgradeCheck = 1130; 218 | TargetAttributes = { 219 | 4321ECBF2421812B00DBA702 = { 220 | CreatedOnToolsVersion = 11.3.1; 221 | TestTargetID = 43D6107C2413D8630089DE4D; 222 | }; 223 | 43D6107C2413D8630089DE4D = { 224 | CreatedOnToolsVersion = 11.3.1; 225 | }; 226 | }; 227 | }; 228 | buildConfigurationList = 43D610782413D8630089DE4D /* Build configuration list for PBXProject "Fingerspelling" */; 229 | compatibilityVersion = "Xcode 9.3"; 230 | developmentRegion = en; 231 | hasScannedForEncodings = 0; 232 | knownRegions = ( 233 | en, 234 | Base, 235 | ); 236 | mainGroup = 43D610742413D8630089DE4D; 237 | productRefGroup = 43D6107E2413D8640089DE4D /* Products */; 238 | projectDirPath = ""; 239 | projectRoot = ""; 240 | targets = ( 241 | 43D6107C2413D8630089DE4D /* Fingerspelling */, 242 | 4321ECBF2421812B00DBA702 /* FingerspellingUITests */, 243 | ); 244 | }; 245 | /* End PBXProject section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | 4321ECBE2421812B00DBA702 /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | 43D6107B2413D8630089DE4D /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 43D6108A2413D8680089DE4D /* Preview Assets.xcassets in Resources */, 260 | 43D610872413D8680089DE4D /* Assets.xcassets in Resources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXResourcesBuildPhase section */ 265 | 266 | /* Begin PBXSourcesBuildPhase section */ 267 | 4321ECBC2421812B00DBA702 /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 4321ECC32421812B00DBA702 /* FingerspellingUITests.swift in Sources */, 272 | 4321ECCD2421A56200DBA702 /* SnapshotHelper.swift in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 43D610792413D8630089DE4D /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 43D610812413D8640089DE4D /* AppDelegate.swift in Sources */, 281 | 4317BBB8242FB99D00A2093D /* CheckmarkAnimationView.swift in Sources */, 282 | 4315BFF12427F9CC002E2C38 /* FeedbackView.swift in Sources */, 283 | 43D610832413D8640089DE4D /* SceneDelegate.swift in Sources */, 284 | 4365109524246A0700EB8937 /* ReceptiveStatsView.swift in Sources */, 285 | 43422CA32421DED30026D68D /* AboutView.swift in Sources */, 286 | 43BCEB4C242115CA0033A56B /* util.swift in Sources */, 287 | 43BCEB3E24210E4C0033A56B /* SettingsView.swift in Sources */, 288 | 43BCEB48242114040033A56B /* GameModeIconView.swift in Sources */, 289 | 43BCEB4024210EEC0033A56B /* NavbarView.swift in Sources */, 290 | 43D610852413D8640089DE4D /* AppView.swift in Sources */, 291 | 43BCEB462421127D0033A56B /* SideMenuView.swift in Sources */, 292 | 43BCEB44242111AE0033A56B /* ScoreIndicatorView.swift in Sources */, 293 | 438EEEBE2427D58600F74F9A /* StatItemView.swift in Sources */, 294 | 43BCEB51242126900033A56B /* Words.swift in Sources */, 295 | 43BCEB4A2421151E0033A56B /* appState.swift in Sources */, 296 | 43BCEB4E242116810033A56B /* viewModifiers.swift in Sources */, 297 | 43BCEB3C24210AB40033A56B /* ExpressiveGameView.swift in Sources */, 298 | 4365109724246A4D00EB8937 /* ExpressiveStatsView.swift in Sources */, 299 | 43BCEB3A2421053A0033A56B /* ReceptiveGameView.swift in Sources */, 300 | 43BCEB422421108B0033A56B /* CurrentWordDisplayView.swift in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | /* End PBXSourcesBuildPhase section */ 305 | 306 | /* Begin PBXTargetDependency section */ 307 | 4321ECC62421812B00DBA702 /* PBXTargetDependency */ = { 308 | isa = PBXTargetDependency; 309 | target = 43D6107C2413D8630089DE4D /* Fingerspelling */; 310 | targetProxy = 4321ECC52421812B00DBA702 /* PBXContainerItemProxy */; 311 | }; 312 | /* End PBXTargetDependency section */ 313 | 314 | /* Begin XCBuildConfiguration section */ 315 | 4321ECC72421812B00DBA702 /* Debug */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | CODE_SIGN_STYLE = Automatic; 319 | DEVELOPMENT_TEAM = X3SA8TV77Y; 320 | INFOPLIST_FILE = FingerspellingUITests/Info.plist; 321 | LD_RUNPATH_SEARCH_PATHS = ( 322 | "$(inherited)", 323 | "@executable_path/Frameworks", 324 | "@loader_path/Frameworks", 325 | ); 326 | PRODUCT_BUNDLE_IDENTIFIER = com.openasl.FingerspellingUITests; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | SWIFT_VERSION = 5.0; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | TEST_TARGET_NAME = Fingerspelling; 331 | }; 332 | name = Debug; 333 | }; 334 | 4321ECC82421812B00DBA702 /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | CODE_SIGN_STYLE = Automatic; 338 | DEVELOPMENT_TEAM = X3SA8TV77Y; 339 | INFOPLIST_FILE = FingerspellingUITests/Info.plist; 340 | LD_RUNPATH_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "@executable_path/Frameworks", 343 | "@loader_path/Frameworks", 344 | ); 345 | PRODUCT_BUNDLE_IDENTIFIER = com.openasl.FingerspellingUITests; 346 | PRODUCT_NAME = "$(TARGET_NAME)"; 347 | SWIFT_VERSION = 5.0; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | TEST_TARGET_NAME = Fingerspelling; 350 | }; 351 | name = Release; 352 | }; 353 | 43D6109A2413D8690089DE4D /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_SEARCH_USER_PATHS = NO; 357 | CLANG_ANALYZER_NONNULL = YES; 358 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 360 | CLANG_CXX_LIBRARY = "libc++"; 361 | CLANG_ENABLE_MODULES = YES; 362 | CLANG_ENABLE_OBJC_ARC = YES; 363 | CLANG_ENABLE_OBJC_WEAK = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 377 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 379 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 380 | CLANG_WARN_STRICT_PROTOTYPES = YES; 381 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 382 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | COPY_PHASE_STRIP = NO; 386 | DEBUG_INFORMATION_FORMAT = dwarf; 387 | ENABLE_STRICT_OBJC_MSGSEND = YES; 388 | ENABLE_TESTABILITY = YES; 389 | GCC_C_LANGUAGE_STANDARD = gnu11; 390 | GCC_DYNAMIC_NO_PIC = NO; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_OPTIMIZATION_LEVEL = 0; 393 | GCC_PREPROCESSOR_DEFINITIONS = ( 394 | "DEBUG=1", 395 | "$(inherited)", 396 | ); 397 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 398 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 399 | GCC_WARN_UNDECLARED_SELECTOR = YES; 400 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 401 | GCC_WARN_UNUSED_FUNCTION = YES; 402 | GCC_WARN_UNUSED_VARIABLE = YES; 403 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 404 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 405 | MTL_FAST_MATH = YES; 406 | ONLY_ACTIVE_ARCH = YES; 407 | SDKROOT = iphoneos; 408 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 409 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 410 | TARGETED_DEVICE_FAMILY = 1; 411 | }; 412 | name = Debug; 413 | }; 414 | 43D6109B2413D8690089DE4D /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | CLANG_ANALYZER_NONNULL = YES; 419 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 420 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 421 | CLANG_CXX_LIBRARY = "libc++"; 422 | CLANG_ENABLE_MODULES = YES; 423 | CLANG_ENABLE_OBJC_ARC = YES; 424 | CLANG_ENABLE_OBJC_WEAK = YES; 425 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 426 | CLANG_WARN_BOOL_CONVERSION = YES; 427 | CLANG_WARN_COMMA = YES; 428 | CLANG_WARN_CONSTANT_CONVERSION = YES; 429 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 430 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 431 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 432 | CLANG_WARN_EMPTY_BODY = YES; 433 | CLANG_WARN_ENUM_CONVERSION = YES; 434 | CLANG_WARN_INFINITE_RECURSION = YES; 435 | CLANG_WARN_INT_CONVERSION = YES; 436 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 438 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 439 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 440 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 441 | CLANG_WARN_STRICT_PROTOTYPES = YES; 442 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 443 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 444 | CLANG_WARN_UNREACHABLE_CODE = YES; 445 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 446 | COPY_PHASE_STRIP = NO; 447 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 448 | ENABLE_NS_ASSERTIONS = NO; 449 | ENABLE_STRICT_OBJC_MSGSEND = YES; 450 | GCC_C_LANGUAGE_STANDARD = gnu11; 451 | GCC_NO_COMMON_BLOCKS = YES; 452 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 453 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 454 | GCC_WARN_UNDECLARED_SELECTOR = YES; 455 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 456 | GCC_WARN_UNUSED_FUNCTION = YES; 457 | GCC_WARN_UNUSED_VARIABLE = YES; 458 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 459 | MTL_ENABLE_DEBUG_INFO = NO; 460 | MTL_FAST_MATH = YES; 461 | SDKROOT = iphoneos; 462 | SWIFT_COMPILATION_MODE = wholemodule; 463 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 464 | TARGETED_DEVICE_FAMILY = 1; 465 | VALIDATE_PRODUCT = YES; 466 | }; 467 | name = Release; 468 | }; 469 | 43D6109D2413D8690089DE4D /* Debug */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 473 | CODE_SIGN_STYLE = Automatic; 474 | CURRENT_PROJECT_VERSION = 14; 475 | DEVELOPMENT_ASSET_PATHS = "\"Fingerspelling/Preview Content\""; 476 | DEVELOPMENT_TEAM = X3SA8TV77Y; 477 | ENABLE_PREVIEWS = YES; 478 | INFOPLIST_FILE = Fingerspelling/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = ( 480 | "$(inherited)", 481 | "@executable_path/Frameworks", 482 | ); 483 | PRODUCT_BUNDLE_IDENTIFIER = com.openasl.Fingerspelling; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | SWIFT_VERSION = 5.0; 486 | TARGETED_DEVICE_FAMILY = "1,2"; 487 | VERSIONING_SYSTEM = "apple-generic"; 488 | }; 489 | name = Debug; 490 | }; 491 | 43D6109E2413D8690089DE4D /* Release */ = { 492 | isa = XCBuildConfiguration; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | CODE_SIGN_STYLE = Automatic; 496 | CURRENT_PROJECT_VERSION = 14; 497 | DEVELOPMENT_ASSET_PATHS = "\"Fingerspelling/Preview Content\""; 498 | DEVELOPMENT_TEAM = X3SA8TV77Y; 499 | ENABLE_PREVIEWS = YES; 500 | INFOPLIST_FILE = Fingerspelling/Info.plist; 501 | LD_RUNPATH_SEARCH_PATHS = ( 502 | "$(inherited)", 503 | "@executable_path/Frameworks", 504 | ); 505 | PRODUCT_BUNDLE_IDENTIFIER = com.openasl.Fingerspelling; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_VERSION = 5.0; 508 | TARGETED_DEVICE_FAMILY = "1,2"; 509 | VERSIONING_SYSTEM = "apple-generic"; 510 | }; 511 | name = Release; 512 | }; 513 | /* End XCBuildConfiguration section */ 514 | 515 | /* Begin XCConfigurationList section */ 516 | 4321ECC92421812B00DBA702 /* Build configuration list for PBXNativeTarget "FingerspellingUITests" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | 4321ECC72421812B00DBA702 /* Debug */, 520 | 4321ECC82421812B00DBA702 /* Release */, 521 | ); 522 | defaultConfigurationIsVisible = 0; 523 | defaultConfigurationName = Release; 524 | }; 525 | 43D610782413D8630089DE4D /* Build configuration list for PBXProject "Fingerspelling" */ = { 526 | isa = XCConfigurationList; 527 | buildConfigurations = ( 528 | 43D6109A2413D8690089DE4D /* Debug */, 529 | 43D6109B2413D8690089DE4D /* Release */, 530 | ); 531 | defaultConfigurationIsVisible = 0; 532 | defaultConfigurationName = Release; 533 | }; 534 | 43D6109C2413D8690089DE4D /* Build configuration list for PBXNativeTarget "Fingerspelling" */ = { 535 | isa = XCConfigurationList; 536 | buildConfigurations = ( 537 | 43D6109D2413D8690089DE4D /* Debug */, 538 | 43D6109E2413D8690089DE4D /* Release */, 539 | ); 540 | defaultConfigurationIsVisible = 0; 541 | defaultConfigurationName = Release; 542 | }; 543 | /* End XCConfigurationList section */ 544 | }; 545 | rootObject = 43D610752413D8630089DE4D /* Project object */; 546 | } 547 | -------------------------------------------------------------------------------- /Fingerspelling.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Fingerspelling.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Fingerspelling.xcodeproj/xcshareddata/xcschemes/Fingerspelling.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Fingerspelling.xcodeproj/xcshareddata/xcschemes/FingerspellingUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 75 | 76 | 77 | 78 | 79 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Fingerspelling.xcodeproj/xcuserdata/sloria.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Fingerspelling.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | FingerspellingUITests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 4321ECBF2421812B00DBA702 21 | 22 | primary 23 | 24 | 25 | 43D6107C2413D8630089DE4D 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Fingerspelling/AboutView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct AboutView: View { 5 | @Environment(\.colorScheme) var colorScheme 6 | 7 | var appVersion: String { 8 | Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" 9 | } 10 | 11 | var bundleVersion: String { 12 | Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" 13 | } 14 | 15 | var about: NSAttributedString { 16 | let attributedString = makeContentString( 17 | "This app was inspired by the website http://asl.ms/ created by Dr. Bill Vicars. If you find this app useful, check out ASLU and consider making a donation.", 18 | colorScheme: self.colorScheme 19 | ) 20 | let aslMsUrl = "http://asl.ms/" 21 | attributedString.addAttribute( 22 | .link, 23 | value: aslMsUrl, 24 | range: NSRange(location: 37, length: aslMsUrl.count) 25 | ) 26 | attributedString.addAttribute( 27 | .link, 28 | value: "https://www.lifeprint.com/", 29 | range: NSRange(location: 119, length: 4) 30 | ) 31 | return attributedString 32 | } 33 | 34 | var privacyPolicy: NSAttributedString { 35 | makeContentString( 36 | "No data or personal information is collected by this app.", 37 | colorScheme: self.colorScheme 38 | ) 39 | } 40 | 41 | private struct Header: ViewModifier { 42 | let font = Font.system(size: 18).weight(.heavy) 43 | func body(content: Content) -> some View { 44 | content 45 | .font(self.font) 46 | .padding(.bottom, 5) 47 | } 48 | } 49 | 50 | var body: some View { 51 | VStack { 52 | Text("ASL Fingerspelling Practice").modifier(Header()) 53 | Text("Version \(self.appVersion) (\(self.bundleVersion))") 54 | .font(.system(size: 12, design: .monospaced)) 55 | AttributedText(self.about) 56 | .padding(.top, 5) 57 | .frame(maxWidth: .infinity, maxHeight: 130, alignment: .leading) 58 | Button(action: self.handleDonate) { 59 | Text("Donate to ASLU").modifier(FullWidthGhostButtonContent()) 60 | } 61 | Divider().padding(.vertical) 62 | Text("Privacy Policy").modifier(Header()) 63 | AttributedText(self.privacyPolicy) 64 | Spacer() 65 | } 66 | .padding() 67 | .navigationBarTitle("About", displayMode: .inline) 68 | } 69 | 70 | private func handleDonate() { 71 | if let url = URL(string: "https://www.lifeprint.com/donate.htm") { 72 | UIApplication.shared.open(url) 73 | } 74 | } 75 | } 76 | 77 | struct AboutView_Previews: PreviewProvider { 78 | static var previews: some View { 79 | AboutView().modifier(RootStyle()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Fingerspelling/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | // Override point for customization after application launch. 7 | true 8 | } 9 | 10 | // MARK: UISceneSession Lifecycle 11 | 12 | func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration { 13 | // Called when a new scene session is being created. 14 | // Use this method to select a configuration to create the new scene with. 15 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 16 | } 17 | 18 | func application(_: UIApplication, didDiscardSceneSessions _: Set) { 19 | // Called when the user discards a scene session. 20 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 21 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fingerspelling/AppView.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import SwiftUI 3 | 4 | struct AppView: View { 5 | @ObservedObject private var keyboard = KeyboardResponder() 6 | 7 | @EnvironmentObject private var settings: UserSettings 8 | @EnvironmentObject private var game: GameState 9 | 10 | private var currentView: AnyView { 11 | switch self.game.mode { 12 | case .receptive: return AnyView(ReceptiveGameView()) 13 | case .expressive: return AnyView(ExpressiveGameView()) 14 | } 15 | } 16 | 17 | private var currentSheet: AnyView { 18 | switch self.game.currentSheet { 19 | case .settings: return AnyView(SettingsView()) 20 | case .receptiveStats: return AnyView(ReceptiveStatsView()) 21 | case .expressiveStats: return AnyView(ExpressiveStatsView()) 22 | } 23 | } 24 | 25 | var body: some View { 26 | ZStack { 27 | self.currentView 28 | .modifier(RootStyle()) 29 | // Move the current UI up when the keyboard is active 30 | .padding(.bottom, self.keyboard.currentHeight) 31 | .sheet(isPresented: self.$game.isShowingSheet) { 32 | self.currentSheet.modifier(SystemServices()) 33 | } 34 | SideMenuView( 35 | width: 280, 36 | isOpen: self.game.isMenuOpen, 37 | onClose: { self.game.isMenuOpen.toggle() } 38 | ) 39 | .edgesIgnoringSafeArea(.all) 40 | .gesture( 41 | // TODO: Make menu move when dragged 42 | DragGesture() 43 | .onEnded { 44 | if $0.translation.width < -100 { 45 | self.game.isMenuOpen = false 46 | } 47 | } 48 | ) 49 | } 50 | .statusBar(hidden: self.game.isMenuOpen) 51 | } 52 | } 53 | 54 | struct AppView_Previews: PreviewProvider { 55 | static var previews: some View { 56 | let playback = SystemServices.playback 57 | let feedback = SystemServices.feedback 58 | 59 | // Modify these during development to update the preview 60 | playback.isPlaying = false 61 | playback.currentWord = "foo" 62 | feedback.isShown = false 63 | 64 | return AppView().modifier(SystemServices()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/A-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/A-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/A-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/A-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/A-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/A-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/A-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "A-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "A-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "A-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iphone_notification20x20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "iphone_notification20x20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "iphone_settings29x29@2x.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "iphone_settings29x29@3x.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "iphone_spotlight40x40@2x.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "iphone_spotlight40x40@3x.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "iphone_app60x60@2x.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "iphone_app60x60@3x.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "ipad_notification20x20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "ipad_notification20x20@2x.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "ipad_settings29x29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "ipad_settings29x29@2x.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "ipad_spotlight40x40.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "ipad_spotlight40x40@2x.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "ipad_app76x76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "ipad_app76x76@2x.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "ipad_pro_app83.5x83.5@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "ios_marketing1024x1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_app76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_app76x76.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_notification20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_notification20x20.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_settings29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_settings29x29.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/B-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/B-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/B-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/B-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/B-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/B-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/B-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "B-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "B-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "B-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/C-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/C-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/C-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/C-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/C-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/C-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/C-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "C-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "C-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "C-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "D-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "D-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "D-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/D-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/D-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/D-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/D-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/D-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/D-lauren-nobg.imageset/D-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "E-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "E-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "E-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/E-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/E-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/E-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/E-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/E-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/E-lauren-nobg.imageset/E-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "F-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "F-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "F-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/F-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/F-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/F-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/F-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/F-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/F-lauren-nobg.imageset/F-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "G-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "G-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "G-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/G-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/G-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/G-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/G-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/G-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/G-lauren-nobg.imageset/G-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "H-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "H-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "H-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/H-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/H-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/H-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/H-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/H-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/H-lauren-nobg.imageset/H-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "I-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "I-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "I-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/I-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/I-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/I-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/I-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/I-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/I-lauren-nobg.imageset/I-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "J-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "J-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "J-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/J-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/J-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/J-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/J-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/J-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/J-lauren-nobg.imageset/J-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "K-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "K-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "K-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/K-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/K-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/K-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/K-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/K-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/K-lauren-nobg.imageset/K-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "L-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "L-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "L-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/L-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/L-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/L-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/L-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/L-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/L-lauren-nobg.imageset/L-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "M-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "M-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "M-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/M-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/M-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/M-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/M-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/M-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/M-lauren-nobg.imageset/M-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "N-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "N-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "N-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/N-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/N-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/N-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/N-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/N-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/N-lauren-nobg.imageset/N-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "O-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "O-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "O-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/O-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/O-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/O-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/O-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/O-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/O-lauren-nobg.imageset/O-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "P-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "P-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "P-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/P-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/P-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/P-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/P-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/P-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/P-lauren-nobg.imageset/P-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Q-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Q-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Q-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Q-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Q-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Q-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Q-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Q-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Q-lauren-nobg.imageset/Q-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "R-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "R-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "R-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/R-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/R-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/R-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/R-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/R-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/R-lauren-nobg.imageset/R-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "S-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "S-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "S-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/S-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/S-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/S-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/S-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/S-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/S-lauren-nobg.imageset/S-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "T-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "T-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "T-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/T-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/T-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/T-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/T-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/T-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/T-lauren-nobg.imageset/T-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "U-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "U-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "U-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/U-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/U-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/U-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/U-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/U-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/U-lauren-nobg.imageset/U-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "V-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "V-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "V-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/V-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/V-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/V-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/V-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/V-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/V-lauren-nobg.imageset/V-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "W-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "W-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "W-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/W-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/W-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/W-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/W-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/W-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/W-lauren-nobg.imageset/W-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "X-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "X-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "X-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/X-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/X-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/X-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/X-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/X-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/X-lauren-nobg.imageset/X-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Y-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Y-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Y-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Y-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Y-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Y-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Y-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Y-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Y-lauren-nobg.imageset/Y-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Z-lauren-nobg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Z-lauren-nobg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Z-lauren-nobg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Z-lauren-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Z-lauren-nobg.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Z-lauren-nobg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Z-lauren-nobg@2x.png -------------------------------------------------------------------------------- /Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Z-lauren-nobg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/Fingerspelling/Assets.xcassets/Z-lauren-nobg.imageset/Z-lauren-nobg@3x.png -------------------------------------------------------------------------------- /Fingerspelling/CheckmarkAnimationView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CheckmarkAnimationView: View { 4 | @State private var displayBorder = false 5 | @State private var displayCheckmark = false 6 | 7 | var size: CGFloat = 100 8 | 9 | var body: some View { 10 | Image(systemName: "checkmark.circle") 11 | .font(Font.system(size: self.size).weight(.light)) 12 | .foregroundColor(.green) 13 | .scaleEffect(displayCheckmark ? 1.5 : 1) 14 | .animation(.spring()) 15 | .onAppear { 16 | self.displayCheckmark.toggle() 17 | } 18 | } 19 | } 20 | 21 | struct CheckmarkAnimationView_Previews: PreviewProvider { 22 | static var previews: some View { 23 | CheckmarkAnimationView() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Fingerspelling/CurrentWordDisplayView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CurrentWordDisplayView: View { 4 | @EnvironmentObject var playback: PlaybackService 5 | 6 | var body: some View { 7 | Text(self.playback.currentWord.uppercased()) 8 | .font(.system(.title, design: .monospaced)) 9 | .minimumScaleFactor(0.8) 10 | .scaledToFill() 11 | } 12 | } 13 | 14 | struct CurrentWordDisplayView_Previews: PreviewProvider { 15 | static var previews: some View { 16 | let playback = SystemServices.playback 17 | 18 | playback.currentWord = "fingerspelling" 19 | 20 | return CurrentWordDisplayView().modifier(RootStyle()).modifier(SystemServices()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Fingerspelling/ExpressiveGameView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ExpressiveGameView: View { 4 | @State var isHighlightingScore = false 5 | 6 | @EnvironmentObject private var game: GameState 7 | @EnvironmentObject private var playback: PlaybackService 8 | @EnvironmentObject private var feedback: FeedbackService 9 | 10 | private var controls: some View { 11 | VStack { 12 | if self.feedback.hasRevealed { 13 | Button(action: self.handleContinue) { 14 | Text("Next word").modifier(FullWidthButtonContent()).padding(.bottom) 15 | } 16 | } 17 | if self.feedback.isRevealed { 18 | Button(action: self.handleHideSpelling) { 19 | Text("Hide").modifier(FullWidthGhostButtonContent()) 20 | } 21 | } else { 22 | Button(action: self.handleRevealSpelling) { 23 | Text("Reveal").modifier(FullWidthGhostButtonContent()) 24 | } 25 | } 26 | } 27 | } 28 | 29 | var body: some View { 30 | VStack { 31 | NavbarView { 32 | Button(action: self.handleToggleStats) { 33 | ScoreIndicatorView( 34 | textContent: String(self.game.expressiveScore), 35 | isHighlighted: self.isHighlightingScore 36 | ) 37 | } 38 | }.modifier(SystemServices()) 39 | 40 | CurrentWordDisplayView() 41 | Spacer() 42 | if self.feedback.isRevealed { 43 | SpellingDisplayView() 44 | } else if !self.feedback.hasRevealed { 45 | Text("Fingerspell the word above.").font(.system(size: 20)) 46 | } 47 | Spacer() 48 | self.controls.padding(.bottom) 49 | } 50 | } 51 | 52 | // MARK: Handlers 53 | 54 | private func handleRevealSpelling() { 55 | self.feedback.reveal() 56 | } 57 | 58 | private func handleHideSpelling() { 59 | self.feedback.hide() 60 | } 61 | 62 | private func handleContinue() { 63 | self.playback.setNextWord() 64 | self.feedback.reset() 65 | self.game.expressiveCompletedWords.append(self.playback.currentWord) 66 | self.isHighlightingScore = true 67 | delayFor(1.0) { 68 | self.isHighlightingScore = false 69 | } 70 | } 71 | 72 | private func handleToggleStats() { 73 | self.game.toggleSheet(.expressiveStats) 74 | self.playback.stop() 75 | } 76 | } 77 | 78 | private struct SpellingDisplayView: View { 79 | @EnvironmentObject var playback: PlaybackService 80 | 81 | static let scaledSize: CGFloat = 165 82 | static let width: CGFloat = 100 83 | 84 | var body: some View { 85 | ScrollView(.horizontal) { 86 | HStack(alignment: .top, spacing: 0) { 87 | ForEach(self.playback.imageNames, id: \.self) { (_ imageName) in 88 | Image(imageName) 89 | .resizable() 90 | .frame(width: Self.scaledSize, height: Self.scaledSize) 91 | // Crop horizontal space around images 92 | .clipped() 93 | .frame(width: Self.width) 94 | } 95 | } 96 | } 97 | .frame(height: 185) 98 | } 99 | } 100 | 101 | struct ExpressiveGameView_Previews: PreviewProvider { 102 | static var previews: some View { 103 | let playback = SystemServices.playback 104 | let feedback = SystemServices.feedback 105 | 106 | // Modify these during development to update the preview 107 | playback.currentWord = "foo" 108 | feedback.reveal() 109 | 110 | return ExpressiveGameView().modifier(RootStyle()).modifier(SystemServices()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Fingerspelling/ExpressiveStatsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ExpressiveStatsView: View { 4 | @EnvironmentObject var game: GameState 5 | 6 | init() { 7 | // Remove extra separators below the list 8 | UITableView.appearance().tableFooterView = UIView() 9 | } 10 | 11 | private var longestWord: String { 12 | self.game.expressiveCompletedWords.max(by: { $0.count < $1.count }) ?? "" 13 | } 14 | 15 | private var averageWordLength: Double { 16 | (self.game.expressiveCompletedWords.map { $0.count }).average 17 | } 18 | 19 | var body: some View { 20 | NavigationView { 21 | List { 22 | StatItemWithIconView( 23 | iconName: "checkmark", 24 | label: "Words completed" 25 | ) { 26 | Text(String(self.game.expressiveScore)) 27 | } 28 | 29 | if !self.game.expressiveCompletedWords.isEmpty { 30 | StatItemView(label: "Longest word") { 31 | Text(self.longestWord.uppercased()) 32 | .font(.system(size: 18, design: .monospaced)) 33 | } 34 | StatItemView(label: "Average word length") { 35 | Text(formatNumber(self.averageWordLength)) 36 | } 37 | } 38 | } 39 | .navigationBarTitle("Stats (Expressive)") 40 | .navigationBarItems(trailing: Button(action: self.handleToggle) { Text("Done") }) 41 | } 42 | } 43 | 44 | private func handleToggle() { 45 | self.game.toggleSheet(.expressiveStats) 46 | } 47 | } 48 | 49 | struct ExpressiveStatsView_Previews: PreviewProvider { 50 | static var previews: some View { 51 | let game = SystemServices.game 52 | game.expressiveCompletedWords = [ 53 | "fly", 54 | "turkey", 55 | "heavy", 56 | ] 57 | return ExpressiveStatsView() 58 | .modifier(SystemServices()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Fingerspelling/FeedbackView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct FeedbackView: View { 5 | @Environment(\.colorScheme) var colorScheme 6 | 7 | static let feedbackEmail = "sloria1+fingerspelling@gmail.com" 8 | 9 | var content: NSAttributedString { 10 | let attributedString = makeContentString( 11 | "Please send all feedback, questions, and ideas to:\n\n\(Self.feedbackEmail)\n\nAll feedback is welcome.", 12 | colorScheme: self.colorScheme 13 | ) 14 | let feedbackUrl = "mailto:\(Self.feedbackEmail)" 15 | attributedString.addAttribute( 16 | .link, 17 | value: feedbackUrl, 18 | range: NSRange(location: 52, length: Self.feedbackEmail.count) 19 | ) 20 | return attributedString 21 | } 22 | 23 | var body: some View { 24 | VStack { 25 | AttributedText(self.content) 26 | Spacer() 27 | } 28 | .padding() 29 | .navigationBarTitle("Send Feedback") 30 | } 31 | } 32 | 33 | struct FeedbackView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | FeedbackView().modifier(RootStyle()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Fingerspelling/GameModeIconView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct GameModeIconView: View { 4 | var mode: GameMode 5 | 6 | private let gameModeIcons = [ 7 | GameMode.receptive: "eyeglasses", 8 | GameMode.expressive: "hand.raised", 9 | ] 10 | 11 | var body: some View { 12 | Image(systemName: self.gameModeIcons[self.mode]!) 13 | } 14 | } 15 | 16 | struct GameModeIconView_Previews: PreviewProvider { 17 | static var previews: some View { 18 | VStack(alignment: .leading) { 19 | HStack { 20 | GameModeIconView(mode: .receptive).frame(minWidth: 35) 21 | Text(GameMode.receptive.rawValue) 22 | } 23 | HStack { 24 | GameModeIconView(mode: .expressive).frame(minWidth: 35) 25 | Text(GameMode.expressive.rawValue) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Fingerspelling/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 2020.8 19 | CFBundleVersion 20 | 14 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UIRequiresFullScreen 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Fingerspelling/NavbarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NavbarView: View { 4 | var fontSize: CGFloat = 14 5 | var content: () -> Content 6 | 7 | @EnvironmentObject private var game: GameState 8 | @EnvironmentObject private var playback: PlaybackService 9 | @EnvironmentObject private var settings: UserSettings 10 | 11 | var body: some View { 12 | VStack { 13 | ZStack { 14 | HStack { 15 | Button(action: self.handleOpenMenu) { 16 | Image(systemName: "line.horizontal.3") 17 | .padding(.trailing, 5) 18 | GameModeIconView(mode: self.game.mode) 19 | .padding(.trailing) 20 | } 21 | Spacer() 22 | Button(action: self.handleToggleSettings) { 23 | Image(systemName: "gear") 24 | .font(Font.body.weight(.bold)) 25 | .padding(.leading, 5) 26 | } 27 | } 28 | self.content() 29 | } 30 | Divider().padding(.bottom, 10) 31 | } 32 | .foregroundColor(.primary) 33 | } 34 | 35 | func handleToggleSettings() { 36 | self.game.toggleSheet(.settings) 37 | self.playback.stop() 38 | } 39 | 40 | func handleOpenMenu() { 41 | self.game.isMenuOpen.toggle() 42 | self.playback.stop() 43 | } 44 | } 45 | 46 | struct NavbarView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | VStack { 49 | NavbarView { 50 | ScoreIndicatorView( 51 | textContent: "42", 52 | isHighlighted: false 53 | ) 54 | } 55 | Spacer() 56 | } 57 | .modifier(RootStyle()) 58 | .modifier(SystemServices()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Fingerspelling/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Fingerspelling/ReceptiveGameView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ReceptiveGameView: View { 4 | /// Timer used to delay playing the next word 5 | @State private var delayTimer: Timer? = nil 6 | 7 | @EnvironmentObject private var game: GameState 8 | @EnvironmentObject private var playback: PlaybackService 9 | @EnvironmentObject private var feedback: FeedbackService 10 | @EnvironmentObject private var settings: UserSettings 11 | @EnvironmentObject var haptics: HapticFeedbackGenerator 12 | 13 | private static let postSubmitDelay = 2.0 // seconds 14 | private static let nextWordDelay = 1.0 // seconds 15 | private static let minSpeed = 1.0 16 | private static let maxSpeed = 11.0 17 | private static let feedbackGenerator = UINotificationFeedbackGenerator() 18 | 19 | private var answerIsCorrect: Bool { 20 | self.answerClean == self.currentWordClean 21 | } 22 | 23 | private var answerIsAlmostCorrect: Bool { 24 | ( 25 | !self.answerIsCorrect && 26 | self.answerClean.levenshteinDistance(to: self.currentWordClean) < 3 27 | ) 28 | } 29 | 30 | private var answerClean: String { 31 | self.feedback.answerTrimmed.lowercased() 32 | } 33 | 34 | private var currentWordClean: String { 35 | self.playback.currentWord.lowercased() 36 | } 37 | 38 | var body: some View { 39 | VStack { 40 | NavbarView { 41 | HStack { 42 | Button(action: self.handleToggleStats) { 43 | ScoreIndicatorView( 44 | textContent: String(self.game.receptiveScore), 45 | isHighlighted: self.feedback.hasCorrectAnswer 46 | ) 47 | .padding(.horizontal, 5) 48 | HStack { 49 | Image(systemName: "metronome") 50 | Text(self.settings.speedDisplay) 51 | .modifier(IndicatorStyle()) 52 | } 53 | .padding(.horizontal, 5) 54 | } 55 | } 56 | }.modifier(SystemServices()) 57 | 58 | if self.feedback.hasCorrectAnswer || self.feedback.isRevealed { 59 | CurrentWordDisplayView() 60 | } 61 | 62 | HStack { 63 | if !self.game.isMenuOpen { 64 | AnswerInput( 65 | value: self.$feedback.answer, 66 | onSubmit: self.handleSubmit, 67 | isCorrect: self.answerIsCorrect, 68 | isAlmostCorrect: self.answerIsAlmostCorrect 69 | ).modifier(SystemServices()) 70 | } 71 | if !self.feedback.shouldDisableControls { 72 | Button(action: self.handleReveal) { 73 | Text("Reveal") 74 | .font(.callout) 75 | .foregroundColor(self.playback.isPlaying ? .gray : .primary) 76 | .frame(height: 30) 77 | }.disabled(self.playback.isPlaying) 78 | } 79 | } 80 | Spacer() 81 | 82 | MainDisplay(onPlay: self.handlePlay) 83 | 84 | Spacer() 85 | SpeedControlView( 86 | value: self.$settings.speed, 87 | minSpeed: Self.minSpeed, 88 | maxSpeed: Self.maxSpeed, 89 | disabled: self.playback.isPlaying 90 | ) 91 | .padding(.bottom, 10) 92 | PlaybackControlView(onPlay: self.handlePlay, onStop: self.handleStop).padding(.bottom, 10) 93 | } 94 | } 95 | 96 | private func playWord() { 97 | self.playback.play() 98 | self.feedback.hide() 99 | } 100 | 101 | // MARK: Handlers 102 | 103 | private func handlePlay() { 104 | self.playWord() 105 | } 106 | 107 | private func handleNextWord() { 108 | self.playback.setNextWordPending() 109 | self.feedback.reset() 110 | 111 | self.delayTimer = delayFor(Self.nextWordDelay) { 112 | self.playWord() 113 | } 114 | } 115 | 116 | private func handleStop() { 117 | self.delayTimer?.invalidate() 118 | self.playback.stop() 119 | self.feedback.hide() 120 | } 121 | 122 | private func handleReveal() { 123 | self.playback.stop() 124 | self.feedback.reveal() 125 | delayFor(Self.postSubmitDelay) { 126 | self.feedback.hide() 127 | self.handleNextWord() 128 | } 129 | } 130 | 131 | private func handleSubmit() { 132 | // Prevent multiple submissions when pressing "return" key 133 | if self.feedback.hasCorrectAnswer { 134 | return 135 | } 136 | self.feedback.show() 137 | if self.answerIsCorrect { 138 | self.handleStop() 139 | self.feedback.markCorrect() 140 | self.game.receptiveCompletedWords.append( 141 | CompletedWord( 142 | self.playback.currentWord, 143 | speed: self.settings.speed 144 | ) 145 | ) 146 | delayFor(Self.postSubmitDelay) { 147 | self.handleNextWord() 148 | } 149 | } else { 150 | self.haptics.generate(self.answerIsAlmostCorrect ? .warning : .error) 151 | self.feedback.markIncorrect() 152 | delayFor(0.5) { 153 | self.feedback.hide() 154 | } 155 | } 156 | } 157 | 158 | private func handleToggleStats() { 159 | self.game.toggleSheet(.receptiveStats) 160 | self.playback.stop() 161 | } 162 | } 163 | 164 | // MARK: Supporting views 165 | 166 | private struct PlaybackControlView: View { 167 | var onPlay: () -> Void 168 | var onStop: () -> Void 169 | 170 | @EnvironmentObject var playback: PlaybackService 171 | @EnvironmentObject var feedback: FeedbackService 172 | 173 | var body: some View { 174 | Group { 175 | if !self.playback.isActive && !self.feedback.shouldDisableControls { 176 | Button(action: self.onPlay) { 177 | Image(systemName: "play.fill") 178 | .font(.system(size: 18)) 179 | .modifier(FullWidthButtonContent()) 180 | } 181 | } else { 182 | Button(action: self.onStop) { 183 | Image(systemName: "stop.fill") 184 | .font(.system(size: 18)) 185 | .modifier(FullWidthGhostButtonContent()) 186 | }.disabled(self.feedback.shouldDisableControls) 187 | } 188 | } 189 | } 190 | } 191 | 192 | private struct MainDisplay: View { 193 | var onPlay: () -> Void 194 | 195 | @EnvironmentObject var playback: PlaybackService 196 | @EnvironmentObject var feedback: FeedbackService 197 | @Environment(\.colorScheme) var colorScheme 198 | 199 | var onboarding: some View { 200 | Group { 201 | Button(action: self.onPlay) { 202 | if !self.playback.hasPlayed { 203 | HStack { 204 | Text("Press ") 205 | Image(systemName: "play").foregroundColor(Color.accentColor) 206 | Text(" to begin.") 207 | } 208 | } else { 209 | Text("Enter the word you saw.") 210 | } 211 | } 212 | } 213 | .font(.system(size: 20)) 214 | .foregroundColor(Color.primary) 215 | .frame(width: 300, height: 150) 216 | } 217 | 218 | var body: some View { 219 | VStack { 220 | if !self.playback.isPlaying { 221 | if !self.feedback.hasSubmitted { 222 | self.onboarding 223 | } else if self.feedback.isShown || self.feedback.hasCorrectAnswer { 224 | FeedbackDisplayView(isCorrect: self.feedback.hasCorrectAnswer) 225 | } else { 226 | if !self.feedback.isRevealed { 227 | // Tapping center of display area replays 228 | GeometryReader { _ in 229 | EmptyView() 230 | } 231 | .frame(minWidth: 200, maxWidth: 250, minHeight: 150, maxHeight: 200) 232 | .background(self.colorScheme == .dark ? Color.black : Color.white) 233 | .onTapGesture { 234 | self.onPlay() 235 | } 236 | } 237 | } 238 | } else { 239 | // Need to pass SystemServices due to a bug in SwiftUI 240 | // re: environment not getting passed to children 241 | WordPlayerView().modifier(SystemServices()) 242 | } 243 | } 244 | } 245 | } 246 | 247 | private struct WordPlayerView: View { 248 | @EnvironmentObject var playback: PlaybackService 249 | @State private var letterOffset: CGFloat = 0 250 | 251 | static let repeatOffset: CGFloat = -20 252 | 253 | var body: some View { 254 | // XXX: Complicated implementation of an animated image 255 | // since there doesn't seem to be a better way to do this in 256 | // SwiftUI yet: https://stackoverflow.com/a/57749621/1157536 257 | Image(uiImage: self.playback.currentLetterImage) 258 | .resizable() 259 | .frame(minWidth: 175, maxWidth: 350, minHeight: 175, maxHeight: 350) 260 | .scaledToFit() 261 | .offset(CGSize(width: self.letterOffset, height: 0)) 262 | .onReceive( 263 | self.playback.playTimer!.publisher, 264 | perform: { _ in 265 | self.playback.setNextLetter() 266 | if self.playback.currentLetterIsRepeat { 267 | self.letterOffset += Self.repeatOffset 268 | } else { 269 | self.letterOffset = 0 270 | } 271 | } 272 | ) 273 | .onAppear { 274 | self.playback.resetTimer() 275 | self.playback.startTimer() 276 | } 277 | .onDisappear { 278 | self.playback.resetTimer() 279 | } 280 | } 281 | } 282 | 283 | private struct FeedbackDisplayView: View { 284 | var isCorrect: Bool 285 | 286 | var body: some View { 287 | Group { 288 | if self.isCorrect { 289 | CheckmarkAnimationView() 290 | } 291 | } 292 | } 293 | } 294 | 295 | private struct SpeedControlView: View { 296 | @Binding var value: Double 297 | 298 | var minSpeed: Double 299 | var maxSpeed: Double 300 | var disabled: Bool 301 | 302 | var body: some View { 303 | HStack { 304 | Image(systemName: "tortoise").foregroundColor(.gray) 305 | Slider(value: self.$value, in: self.minSpeed ... self.maxSpeed, step: 1) 306 | .disabled(self.disabled) 307 | Image(systemName: "hare").foregroundColor(.gray) 308 | } 309 | } 310 | } 311 | 312 | private struct AnswerInput: View { 313 | @Binding var value: String 314 | var onSubmit: () -> Void 315 | var isCorrect: Bool = true 316 | var isAlmostCorrect: Bool = true 317 | var disabled: Bool = false 318 | 319 | @EnvironmentObject var feedback: FeedbackService 320 | 321 | var body: some View { 322 | FocusableTextField( 323 | text: self.$value, 324 | isFirstResponder: true, 325 | placeholder: "WORD", 326 | textFieldShouldReturn: { _ in 327 | self.onSubmit() 328 | return true 329 | }, 330 | modifyTextField: { textField in 331 | textField.borderStyle = .roundedRect 332 | textField.autocapitalizationType = .allCharacters 333 | textField.autocorrectionType = .no 334 | textField.returnKeyType = .done 335 | textField.keyboardType = .asciiCapable 336 | let font = UIFont.preferredFont(forTextStyle: .title3) 337 | textField.font = .monospacedSystemFont(ofSize: font.pointSize, weight: .regular) 338 | textField.clearButtonMode = .whileEditing 339 | return textField 340 | }, 341 | onUpdate: { textField in 342 | // Uppercase all input 343 | textField.text = self.value.uppercased() 344 | 345 | if self.feedback.isShown, !self.isCorrect { 346 | if self.isAlmostCorrect { 347 | // Highlight 348 | textField.layer.cornerRadius = 4.0 349 | textField.layer.borderColor = UIColor.systemYellow.withAlphaComponent(0.4).cgColor 350 | textField.layer.borderWidth = 2.0 351 | } 352 | 353 | // Shake input if incorrect 354 | let shake = CABasicAnimation(keyPath: "position") 355 | shake.duration = 0.05 356 | shake.repeatCount = self.isAlmostCorrect ? 1 : 2 357 | shake.autoreverses = true 358 | let displacement: CGFloat = 7 359 | shake.fromValue = NSValue(cgPoint: CGPoint(x: textField.center.x - displacement, y: textField.center.y)) 360 | shake.toValue = NSValue(cgPoint: CGPoint(x: textField.center.x + displacement, y: textField.center.y)) 361 | textField.layer.add(shake, forKey: "position") 362 | 363 | } else { 364 | // Remove highlight 365 | textField.layer.borderColor = nil 366 | textField.layer.borderWidth = 0 367 | } 368 | } 369 | ) 370 | // Hide input after success. 371 | // Note: we use opacity to hide because the text field needs to be present for the keyboard 372 | // to remain on the screen and we set the frame to 0 to make room for the correct word display. 373 | .frame( 374 | maxWidth: self.feedback.shouldDisableControls ? 0 : 280, 375 | maxHeight: self.feedback.hasCorrectAnswer ? 0 : 30 376 | ) 377 | .opacity(self.feedback.shouldDisableControls ? 0 : 1) 378 | } 379 | } 380 | 381 | struct ReceptiveGameView_Previews: PreviewProvider { 382 | static var previews: some View { 383 | let playback = SystemServices.playback 384 | let feedback = SystemServices.feedback 385 | 386 | // Modify these during development to update the preview 387 | playback.isPlaying = false 388 | playback.currentWord = "foo" 389 | feedback.hasSubmitted = false 390 | feedback.isShown = false 391 | 392 | return ReceptiveGameView().modifier(RootStyle()).modifier(SystemServices()) 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /Fingerspelling/ReceptiveStatsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ReceptiveStatsView: View { 4 | @EnvironmentObject var game: GameState 5 | @EnvironmentObject var settings: UserSettings 6 | 7 | init() { 8 | // Remove extra separators below the list 9 | UITableView.appearance().tableFooterView = UIView() 10 | } 11 | 12 | private var longestWord: String { 13 | self.game.receptiveCompletedWords.max(by: { $0.word.count < $1.word.count })?.word ?? "" 14 | } 15 | 16 | private var averageWordLength: Double { 17 | (self.game.receptiveCompletedWords.map { $0.word.count }).average 18 | } 19 | 20 | private var fastestSpeed: Double { 21 | self.game.receptiveCompletedWords.max(by: { $0.speed < $1.speed })?.speed ?? 0.0 22 | } 23 | 24 | private var averageSpeed: Double { 25 | (self.game.receptiveCompletedWords.map { $0.speed }).average 26 | } 27 | 28 | var body: some View { 29 | NavigationView { 30 | List { 31 | StatItemWithIconView( 32 | iconName: "checkmark", 33 | label: "Words completed" 34 | ) { 35 | Text(String(self.game.receptiveScore)) 36 | } 37 | StatItemWithIconView( 38 | iconName: "metronome", 39 | label: "Current speed" 40 | ) { 41 | Text(self.settings.speedDisplay) 42 | } 43 | 44 | if !self.game.receptiveCompletedWords.isEmpty { 45 | StatItemView(label: "Longest word") { 46 | Text(self.longestWord.uppercased()) 47 | .font(.system(size: 18, design: .monospaced)) 48 | } 49 | 50 | StatItemView(label: "Average word length") { 51 | Text(formatNumber(self.averageWordLength)) 52 | } 53 | 54 | StatItemView(label: "Fastest speed") { 55 | Text(formatNumber(self.fastestSpeed)) 56 | } 57 | 58 | StatItemView(label: "Average speed") { 59 | Text(formatNumber(self.averageSpeed)) 60 | } 61 | } 62 | } 63 | .navigationBarTitle("Stats (Receptive)") 64 | .navigationBarItems(trailing: Button(action: self.handleToggle) { Text("Done") }) 65 | .listRowInsets(nil) 66 | } 67 | } 68 | 69 | private func handleToggle() { 70 | self.game.toggleSheet(.receptiveStats) 71 | } 72 | } 73 | 74 | struct ReceptiveStatsView_Previews: PreviewProvider { 75 | static var previews: some View { 76 | let game = SystemServices.game 77 | game.receptiveCompletedWords = [ 78 | CompletedWord("fly", speed: 3.0), 79 | CompletedWord("turkey", speed: 3.0), 80 | CompletedWord("heavy", speed: 4.0), 81 | ] 82 | return ReceptiveStatsView() 83 | .modifier(SystemServices()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Fingerspelling/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import UIKit 3 | 4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 5 | var window: UIWindow? 6 | 7 | func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { 8 | // Modify initial state for testing/screenshots 9 | #if DEBUG 10 | if isUITesting() { 11 | let game = SystemServices.game 12 | let settings = SystemServices.settings 13 | for _ in 0 ..< 27 { 14 | game.receptiveCompletedWords.append(CompletedWord("abcde", speed: 4.0)) 15 | } 16 | game.receptiveCompletedWords.append(CompletedWord("turkey", speed: 8.0)) 17 | for _ in 0 ..< 12 { 18 | game.expressiveCompletedWords.append("abcde") 19 | } 20 | settings.speed = 3.0 21 | } 22 | #endif 23 | 24 | let contentView = AppView() 25 | .modifier(SystemServices()) 26 | 27 | // Use a UIHostingController as window root view controller. 28 | if let windowScene = scene as? UIWindowScene { 29 | let window = UIWindow(windowScene: windowScene) 30 | window.rootViewController = UIHostingController(rootView: contentView) 31 | self.window = window 32 | window.makeKeyAndVisible() 33 | } 34 | } 35 | 36 | func sceneDidDisconnect(_: UIScene) {} 37 | 38 | func sceneDidBecomeActive(_: UIScene) {} 39 | 40 | func sceneWillResignActive(_: UIScene) {} 41 | 42 | func sceneWillEnterForeground(_: UIScene) {} 43 | 44 | func sceneDidEnterBackground(_: UIScene) {} 45 | } 46 | -------------------------------------------------------------------------------- /Fingerspelling/ScoreIndicatorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct ScoreIndicatorView: View { 5 | var textContent: String 6 | var isHighlighted: Bool = false 7 | 8 | var body: some View { 9 | HStack { 10 | Image(systemName: "checkmark") 11 | .foregroundColor(self.isHighlighted ? Color.green : .primary) 12 | Text(self.textContent) 13 | .fontWeight(self.isHighlighted ? .bold : .regular) 14 | .modifier(IndicatorStyle()) 15 | } 16 | } 17 | } 18 | 19 | struct ScoreIndicatorView_Previews: PreviewProvider { 20 | static var previews: some View { 21 | HStack(spacing: 20) { 22 | ScoreIndicatorView(textContent: "42", isHighlighted: true) 23 | ScoreIndicatorView(textContent: "24", isHighlighted: false) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Fingerspelling/SettingsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SettingsView: View { 4 | @State var isShowingFeedbackAlert = false 5 | @EnvironmentObject private var settings: UserSettings 6 | @EnvironmentObject private var game: GameState 7 | @EnvironmentObject private var playback: PlaybackService 8 | 9 | static let wordLengths = Array(3 ... 8) + [Int.max] 10 | static let appId = "id1503242863" 11 | 12 | struct LabeledPicker: View { 13 | var selection: Binding 14 | var label: String 15 | var content: () -> Content 16 | 17 | var body: some View { 18 | Section(header: Text(self.label.uppercased())) { 19 | Picker(selection: self.selection, label: Text(self.label)) { 20 | self.content() 21 | }.pickerStyle(SegmentedPickerStyle()) 22 | } 23 | } 24 | } 25 | 26 | var body: some View { 27 | NavigationView { 28 | Form { 29 | LabeledPicker(selection: self.$settings.maxWordLength, label: "Max Word Length (Letters)") { 30 | ForEach(Self.wordLengths, id: \.self) { 31 | Text($0 == Int.max ? "Any" : String($0)).tag($0) 32 | } 33 | } 34 | 35 | Section { 36 | Toggle(isOn: self.$settings.enableHaptics) { 37 | Text("Enable Haptics") 38 | } 39 | } 40 | 41 | Section { 42 | NavigationLink(destination: FeedbackView()) { 43 | Text("Send Feedback").fontWeight(.semibold) 44 | } 45 | Button(action: self.handleRate) { 46 | HStack { 47 | Text("Rate Fingerspelling").foregroundColor(.primary) 48 | Spacer() 49 | Image(systemName: "arrow.up.right").foregroundColor(.gray) 50 | } 51 | } 52 | NavigationLink(destination: AboutView()) { Text("About") } 53 | } 54 | } 55 | .navigationBarTitle("Settings") 56 | .navigationBarItems(trailing: Button(action: self.handleToggleSettings) { Text("Done") }) 57 | } 58 | } 59 | 60 | private func handleToggleSettings() { 61 | self.game.toggleSheet(.settings) 62 | self.playback.stop() 63 | } 64 | 65 | private func handleRate() { 66 | if let url = URL(string: "https://itunes.apple.com/us/app/appName/\(Self.appId)?mt=8&action=write-review"), 67 | UIApplication.shared.canOpenURL(url) { 68 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 69 | } 70 | } 71 | } 72 | 73 | struct SettingsView_Previews: PreviewProvider { 74 | static var previews: some View { 75 | SettingsView().modifier(SystemServices()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Fingerspelling/SideMenuView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SideMenuView: View { 4 | let width: CGFloat 5 | let isOpen: Bool 6 | let onClose: () -> Void 7 | 8 | @Environment(\.colorScheme) var colorScheme 9 | 10 | struct MenuContentView: View { 11 | @EnvironmentObject var game: GameState 12 | @EnvironmentObject var feedback: FeedbackService 13 | @EnvironmentObject var playback: PlaybackService 14 | 15 | struct ItemButtonView: View { 16 | var action: () -> Void 17 | var content: () -> Content 18 | 19 | var body: some View { 20 | Button(action: self.action) { 21 | HStack { 22 | self.content() 23 | Spacer() 24 | }.frame(minWidth: 0, maxWidth: .infinity) 25 | } 26 | .padding(.vertical, 20) 27 | } 28 | } 29 | 30 | var body: some View { 31 | VStack(alignment: .leading, spacing: 0) { 32 | Text("Mode") 33 | .font(.title) 34 | .fontWeight(.bold) 35 | .padding(.bottom, 15) 36 | 37 | ForEach(GameMode.allCases, id: \.self) { mode in 38 | ItemButtonView(action: { 39 | self.changeGameMode(mode) 40 | }) { 41 | Group { 42 | GameModeIconView(mode: mode) 43 | .imageScale(.large).frame(minWidth: 35) 44 | Text(mode.rawValue) 45 | .fontWeight(self.game.mode == mode ? .bold : .regular) 46 | } 47 | } 48 | } 49 | Spacer() 50 | } 51 | .font(.system(size: 20)) 52 | .foregroundColor(.primary) 53 | .padding() 54 | .frame(maxWidth: .infinity, alignment: .leading) 55 | .edgesIgnoringSafeArea(.all) 56 | } 57 | 58 | func changeGameMode(_ gameMode: GameMode) { 59 | self.game.mode = gameMode 60 | self.game.isMenuOpen.toggle() 61 | self.playback.reset() 62 | self.feedback.reset() 63 | self.feedback.hasSubmitted = false 64 | } 65 | } 66 | 67 | var body: some View { 68 | ZStack { 69 | GeometryReader { _ in 70 | EmptyView() 71 | } 72 | .background(Color.black.opacity(0.5)) 73 | .opacity(self.isOpen ? 1.0 : 0.0) 74 | .animation(.easeIn(duration: 0.2)) 75 | .onTapGesture { 76 | self.onClose() 77 | } 78 | 79 | HStack { 80 | MenuContentView() 81 | .frame(width: self.width) 82 | .padding(.top, 50) 83 | .background(self.colorScheme == .dark ? Color.darkGrey : Color.white) 84 | .offset(x: self.isOpen ? 0 : -self.width) 85 | .animation(.easeOut(duration: 0.2)) 86 | Spacer() 87 | } 88 | } 89 | } 90 | } 91 | 92 | struct SideMenuView_Previews: PreviewProvider { 93 | static var previews: some View { 94 | Group { 95 | SideMenuView(width: 280, isOpen: true, onClose: {}) 96 | .modifier(SystemServices()) 97 | SideMenuView(width: 280, isOpen: true, onClose: {}) 98 | .background(Color.black) 99 | .environment(\.colorScheme, .dark) 100 | .modifier(SystemServices()) 101 | } 102 | .edgesIgnoringSafeArea(.all) 103 | .statusBar(hidden: true) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Fingerspelling/StatItemView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct StatItemView: View { 4 | var label: String 5 | var content: () -> Content 6 | 7 | var body: some View { 8 | HStack { 9 | Text(self.label).padding(.leading, 30) 10 | Spacer() 11 | self.content() 12 | } 13 | } 14 | } 15 | 16 | struct StatItemWithIconView: View { 17 | var iconName: String 18 | var label: String 19 | var content: () -> Content 20 | 21 | var body: some View { 22 | HStack { 23 | Image(systemName: self.iconName).frame(width: 20) 24 | Text(self.label) 25 | Spacer() 26 | self.content() 27 | } 28 | } 29 | } 30 | 31 | struct StatItemView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | List { 34 | StatItemWithIconView(iconName: "checkmark", label: "Words completed") { 35 | Text("42") 36 | } 37 | StatItemView(label: "Average speed") { 38 | Text("4.2") 39 | } 40 | StatItemView(label: "Longest word") { 41 | Text("FINGERSPELLING").font(.system(size: 18, design: .monospaced)) 42 | } 43 | }.padding() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Fingerspelling/appState.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import SwiftUI 3 | 4 | /// View modifier to set all of the main services on the environment 5 | /// https://medium.com/swlh/swiftui-and-the-missing-environment-object-1a4bf8913ba7 6 | struct SystemServices: ViewModifier { 7 | static var game = GameState() 8 | static var playback = PlaybackService() 9 | static var feedback = FeedbackService() 10 | static var settings = UserSettings() 11 | static var haptics = HapticFeedbackGenerator() 12 | 13 | func body(content: Content) -> some View { 14 | content 15 | .environmentObject(Self.game) 16 | .environmentObject(Self.settings) 17 | .environmentObject(Self.playback) 18 | .environmentObject(Self.feedback) 19 | .environmentObject(Self.haptics) 20 | } 21 | } 22 | 23 | enum GameMode: String, CaseIterable { 24 | case receptive = "Receptive" 25 | case expressive = "Expressive" 26 | } 27 | 28 | enum Sheet { 29 | case settings 30 | case receptiveStats 31 | case expressiveStats 32 | } 33 | 34 | struct CompletedWord { 35 | var word: String 36 | var speed: Double 37 | 38 | init(_ word: String, speed: Double) { 39 | self.word = word 40 | self.speed = speed 41 | } 42 | } 43 | 44 | final class GameState: ObservableObject { 45 | @Published var receptiveCompletedWords: [CompletedWord] = [] 46 | @Published var expressiveCompletedWords: [String] = [] 47 | @Published var isShowingSheet = false 48 | @Published var isMenuOpen = false 49 | @Published var mode: GameMode = .receptive 50 | @Published var currentSheet: Sheet = .settings 51 | 52 | var receptiveScore: Int { 53 | self.receptiveCompletedWords.count 54 | } 55 | 56 | var expressiveScore: Int { 57 | self.expressiveCompletedWords.count 58 | } 59 | 60 | func toggleSheet(_ sheet: Sheet) { 61 | self.currentSheet = sheet 62 | self.isShowingSheet.toggle() 63 | } 64 | } 65 | 66 | final class PlaybackService: ObservableObject { 67 | @Published var currentWord = "" 68 | @Published var letterIndex = 0 69 | @Published var isPlaying = false 70 | @Published var playTimer: LoadingTimer? 71 | @Published var isPendingNextWord: Bool = false 72 | @Published var hasPlayed = false 73 | 74 | private var settings = SystemServices.settings 75 | private static let numerator = 2.0 // Higher value = slower speeds 76 | 77 | init() { 78 | self.currentWord = getRandomWord() 79 | self.playTimer = self.getTimer() 80 | } 81 | 82 | var currentLetterImage: UIImage { 83 | self.uiImages[self.letterIndex] 84 | } 85 | 86 | var currentLetterIsRepeat: Bool { 87 | self.letterIndex > 0 && 88 | Array(self.currentWord)[self.letterIndex - 1] == Array(self.currentWord)[self.letterIndex] 89 | } 90 | 91 | var isActive: Bool { 92 | self.isPlaying || self.isPendingNextWord 93 | } 94 | 95 | var imageNames: [String] { 96 | Array(self.currentWord).map { "\(String($0).uppercased())-lauren-nobg" } 97 | } 98 | 99 | private var uiImages: [UIImage] { 100 | self.imageNames.map { UIImage(named: $0)! } 101 | } 102 | 103 | func reset() { 104 | self.stop() 105 | self.setNextWord() 106 | self.hasPlayed = false 107 | } 108 | 109 | func play() { 110 | self.letterIndex = 0 111 | self.isPlaying = true 112 | self.isPendingNextWord = false 113 | self.hasPlayed = true 114 | } 115 | 116 | func stop() { 117 | self.resetTimer() 118 | self.letterIndex = 0 119 | self.isPlaying = false 120 | self.isPendingNextWord = false 121 | } 122 | 123 | func setNextLetter() { 124 | if self.letterIndex >= (self.uiImages.count - 1) { 125 | self.isPlaying = false 126 | } else { 127 | self.letterIndex += 1 128 | } 129 | } 130 | 131 | func setNextWordPending() { 132 | self.setNextWord() 133 | self.isPendingNextWord = true 134 | } 135 | 136 | func setNextWord() { 137 | self.currentWord = getRandomWord() 138 | } 139 | 140 | func startTimer() { 141 | self.playTimer!.start() 142 | } 143 | 144 | func resetTimer() { 145 | self.playTimer!.cancel() 146 | self.playTimer = self.getTimer() 147 | } 148 | 149 | private func getTimer() -> LoadingTimer { 150 | let every = Self.numerator / self.settings.speed 151 | return LoadingTimer(every: every) 152 | } 153 | } 154 | 155 | final class FeedbackService: ObservableObject { 156 | @Published var answer: String = "" 157 | @Published var isShown: Bool = false 158 | @Published var hasCorrectAnswer: Bool = false 159 | @Published var hasRevealed: Bool = false 160 | @Published var hasSubmitted: Bool = false 161 | @Published var isRevealed: Bool = false 162 | 163 | var shouldDisableControls: Bool { 164 | self.hasCorrectAnswer || self.isRevealed 165 | } 166 | 167 | var answerTrimmed: String { 168 | self.answer.trimmingCharacters(in: .whitespaces) 169 | } 170 | 171 | func reset() { 172 | self.answer = "" 173 | self.hasCorrectAnswer = false 174 | self.isShown = false 175 | self.hasRevealed = false 176 | self.isRevealed = false 177 | } 178 | 179 | func show() { 180 | self.isShown = true 181 | } 182 | 183 | func hide() { 184 | self.isShown = false 185 | self.isRevealed = false 186 | } 187 | 188 | func reveal() { 189 | self.hasRevealed = true 190 | self.isRevealed = true 191 | self.isShown = false 192 | self.hasSubmitted = true // avoid showing onboarding 193 | } 194 | 195 | func markCorrect() { 196 | self.hasCorrectAnswer = true 197 | self.hasSubmitted = true 198 | } 199 | 200 | func markIncorrect() { 201 | self.hasCorrectAnswer = false 202 | self.hasSubmitted = true 203 | } 204 | } 205 | 206 | /// Simple wrapper around UserDefaults to make settings observables 207 | final class UserSettings: ObservableObject { 208 | let objectWillChange = PassthroughSubject() 209 | 210 | private var playback: PlaybackService { 211 | SystemServices.playback 212 | } 213 | 214 | private var feedback: FeedbackService { 215 | SystemServices.feedback 216 | } 217 | 218 | var speedDisplay: String { 219 | String(Int(self.speed)) 220 | } 221 | 222 | init() { 223 | Words = AllWords.filter { $0.count <= self.maxWordLength } 224 | } 225 | 226 | // Settings go here 227 | 228 | @UserDefault("speed", defaultValue: 3.0) 229 | var speed: Double { 230 | willSet { 231 | self.objectWillChange.send() 232 | } 233 | } 234 | 235 | @UserDefault("maxWordLength", defaultValue: Int.max) 236 | var maxWordLength: Int { 237 | willSet { 238 | Words = AllWords.filter { $0.count <= newValue } 239 | self.playback.setNextWord() 240 | if !self.feedback.hasSubmitted { 241 | self.playback.hasPlayed = false 242 | } 243 | self.feedback.reset() 244 | self.objectWillChange.send() 245 | } 246 | } 247 | 248 | @UserDefault("enableHaptics", defaultValue: true) 249 | var enableHaptics: Bool { 250 | willSet { 251 | self.objectWillChange.send() 252 | } 253 | } 254 | } 255 | 256 | /// UINotificationFeedbackGenerator proxy that reads UserDefaults for the haptics preference 257 | final class HapticFeedbackGenerator: ObservableObject { 258 | private var settings = SystemServices.settings 259 | 260 | private static var generator = UINotificationFeedbackGenerator() 261 | 262 | func generate(_ type: UINotificationFeedbackGenerator.FeedbackType) { 263 | if self.settings.enableHaptics { Self.generator.notificationOccurred(type) } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /Fingerspelling/util.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import Foundation 3 | import SwiftUI 4 | 5 | /// Get a random word from the Words list 6 | func getRandomWord() -> String { 7 | #if DEBUG 8 | if isUITesting() { 9 | let word = __testWords[__wordIndexForTesting % __testWords.count] 10 | __wordIndexForTesting += 1 11 | return word 12 | } 13 | #endif 14 | let word = Words.randomElement()! 15 | print("current word: " + word) 16 | return word 17 | } 18 | 19 | #if DEBUG 20 | private var __wordIndexForTesting = 0 21 | private var __testWords = ["turkey", "fly", "heavy"] 22 | 23 | func isUITesting() -> Bool { 24 | ProcessInfo.processInfo.arguments.contains("testing") 25 | } 26 | #endif 27 | 28 | class LoadingTimer { 29 | var publisher: Timer.TimerPublisher 30 | private var timerCancellable: Cancellable? 31 | 32 | init(every: Double) { 33 | self.publisher = Timer.publish(every: every, on: .main, in: .default) 34 | self.timerCancellable = nil 35 | } 36 | 37 | func start() { 38 | self.timerCancellable = self.publisher.connect() 39 | } 40 | 41 | func cancel() { 42 | self.timerCancellable?.cancel() 43 | } 44 | } 45 | 46 | @discardableResult 47 | func delayFor(_ seconds: Double, onComplete: @escaping () -> Void) -> Timer { 48 | Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { _ in 49 | onComplete() 50 | } 51 | } 52 | 53 | // https://stackoverflow.com/a/57029469/1157536 54 | @propertyWrapper 55 | struct UserDefault { 56 | let key: String 57 | let defaultValue: T 58 | 59 | init(_ key: String, defaultValue: T) { 60 | self.key = key 61 | self.defaultValue = defaultValue 62 | } 63 | 64 | var wrappedValue: T { 65 | get { 66 | UserDefaults.standard.object(forKey: self.key) as? T ?? self.defaultValue 67 | } 68 | set { 69 | UserDefaults.standard.set(newValue, forKey: self.key) 70 | } 71 | } 72 | } 73 | 74 | // https://stackoverflow.com/questions/56491881/move-textfield-up-when-thekeyboard-has-appeared-by-using-swiftui-ios 75 | final class KeyboardResponder: ObservableObject { 76 | private var notificationCenter: NotificationCenter 77 | @Published private(set) var currentHeight: CGFloat = 0 78 | 79 | init(center: NotificationCenter = .default) { 80 | self.notificationCenter = center 81 | self.notificationCenter.addObserver(self, selector: #selector(self.keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) 82 | self.notificationCenter.addObserver(self, selector: #selector(self.keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) 83 | } 84 | 85 | deinit { 86 | notificationCenter.removeObserver(self) 87 | } 88 | 89 | @objc func keyBoardWillShow(notification: Notification) { 90 | if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { 91 | self.currentHeight = keyboardSize.height 92 | } 93 | } 94 | 95 | @objc func keyBoardWillHide(notification _: Notification) { 96 | self.currentHeight = 0 97 | } 98 | } 99 | 100 | // adapted from https://stackoverflow.com/a/56508132/1157536 101 | struct FocusableTextField: UIViewRepresentable { 102 | class Coordinator: NSObject, UITextFieldDelegate { 103 | @Binding var text: String 104 | var didBecomeFirstResponder = false 105 | var maxLength: Int 106 | var _textFieldShouldReturn: (_ textField: UITextField) -> Bool 107 | 108 | init(text: Binding, maxLength: Int, textFieldShouldReturn: @escaping (_ textField: UITextField) -> Bool) { 109 | self._text = text 110 | self.maxLength = maxLength 111 | self._textFieldShouldReturn = textFieldShouldReturn 112 | } 113 | 114 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 115 | guard let text = textField.text else { return true } 116 | let newLength = text.count + string.count - range.length 117 | return newLength <= self.maxLength 118 | } 119 | 120 | func textFieldDidChangeSelection(_ textField: UITextField) { 121 | DispatchQueue.main.async { 122 | self.text = textField.text ?? "" 123 | } 124 | } 125 | 126 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 127 | self._textFieldShouldReturn(textField) 128 | } 129 | } 130 | 131 | @Binding var text: String 132 | var isFirstResponder: Bool = false 133 | var placeholder: String = "" 134 | var textFieldShouldReturn: (_ textField: UITextField) -> Bool = { _ in true } 135 | var modifyTextField: (_ textField: UITextField) -> UITextField = { (_ textField) in textField } 136 | var onUpdate: (_ textField: UITextField) -> Void = { _ in } 137 | var maxLength = 14 138 | 139 | func makeUIView(context: UIViewRepresentableContext) -> UITextField { 140 | let textField = UITextField(frame: .zero) 141 | textField.delegate = context.coordinator 142 | textField.placeholder = self.placeholder 143 | return self.modifyTextField(textField) 144 | } 145 | 146 | func makeCoordinator() -> FocusableTextField.Coordinator { 147 | Coordinator( 148 | text: self.$text, 149 | maxLength: self.maxLength, 150 | textFieldShouldReturn: self.textFieldShouldReturn 151 | ) 152 | } 153 | 154 | func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext) { 155 | if self.isFirstResponder, !context.coordinator.didBecomeFirstResponder { 156 | uiView.becomeFirstResponder() 157 | context.coordinator.didBecomeFirstResponder = true 158 | } 159 | self.onUpdate(uiView) 160 | } 161 | } 162 | 163 | struct AttributedText: UIViewRepresentable { 164 | var attributedText: NSAttributedString 165 | 166 | init(_ attributedText: NSAttributedString) { 167 | self.attributedText = attributedText 168 | } 169 | 170 | func makeUIView(context _: UIViewRepresentableContext) -> UITextView { 171 | let textView = UITextView() 172 | textView.isEditable = false 173 | return textView 174 | } 175 | 176 | func updateUIView(_ label: UITextView, context _: Context) { 177 | label.attributedText = self.attributedText 178 | } 179 | } 180 | 181 | func makeContentString(_ text: String, colorScheme: ColorScheme) -> NSMutableAttributedString { 182 | let attributedString = NSMutableAttributedString( 183 | string: text, 184 | attributes: [ 185 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18), 186 | NSAttributedString.Key.foregroundColor: colorScheme == .dark ? UIColor.white : UIColor.black, 187 | ] 188 | ) 189 | let parStyle = NSMutableParagraphStyle() 190 | parStyle.lineSpacing = 2.0 191 | parStyle.lineBreakMode = .byWordWrapping 192 | attributedString.addAttribute( 193 | .paragraphStyle, 194 | value: parStyle, 195 | range: NSRange(location: 0, length: attributedString.length) 196 | ) 197 | return attributedString 198 | } 199 | 200 | func rounded(_ number: Double, places: Int) -> Double { 201 | let factor = pow(10.0, Double(places)) 202 | return Double(round(factor * number) / factor) 203 | } 204 | 205 | /// Return number as a formatted string suitable for display as a statistic. 206 | func formatNumber(_ number: Double, places: Int = 1) -> String { 207 | let roundedNumber = rounded(number, places: places) 208 | return roundedNumber == number ? String(Int(number)) : String(roundedNumber) 209 | } 210 | 211 | // MARK: Extensions 212 | 213 | extension String { 214 | // https://stackoverflow.com/a/44102415/1157536 215 | func levenshteinDistance(to: String) -> Int { 216 | let empty = [Int](repeating: 0, count: to.count) 217 | var last = [Int](0 ... to.count) 218 | 219 | for (i, char1) in self.enumerated() { 220 | var cur = [i + 1] + empty 221 | for (j, char2) in to.enumerated() { 222 | cur[j + 1] = char1 == char2 ? last[j] : Swift.min(last[j], last[j + 1], cur[j]) + 1 223 | } 224 | last = cur 225 | } 226 | return last.last! 227 | } 228 | } 229 | 230 | extension Color { 231 | static let darkGrey = Color(red: 40 / 255, green: 40 / 255, blue: 40 / 255) 232 | } 233 | 234 | extension Collection where Element: Numeric { 235 | /// Returns the total sum of all elements in the array 236 | var total: Element { reduce(0, +) } 237 | } 238 | 239 | extension Collection where Element: BinaryInteger { 240 | /// Returns the average of all elements in the array 241 | var average: Double { isEmpty ? 0 : Double(self.total) / Double(count) } 242 | } 243 | 244 | extension Collection where Element: BinaryFloatingPoint { 245 | /// Returns the average of all elements in the array 246 | var average: Element { isEmpty ? 0 : self.total / Element(count) } 247 | } 248 | -------------------------------------------------------------------------------- /Fingerspelling/viewModifiers.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct RootStyle: ViewModifier { 4 | func body(content: Content) -> some View { 5 | content 6 | .padding(.top, 10) 7 | .padding(.horizontal, 20) 8 | } 9 | } 10 | 11 | struct IndicatorStyle: ViewModifier { 12 | func body(content: Content) -> some View { 13 | content 14 | .font(.system(.callout, design: .monospaced)) 15 | } 16 | } 17 | 18 | struct FullWidthButtonContent: ViewModifier { 19 | var background: Color = Color.accentColor 20 | var foregroundColor: Color = Color.white 21 | var disabled: Bool = false 22 | 23 | func body(content: Content) -> some View { 24 | content 25 | .frame(minWidth: 0, maxWidth: .infinity) 26 | .padding() 27 | .background(self.background) 28 | .foregroundColor(self.foregroundColor) 29 | .cornerRadius(40) 30 | .opacity(self.disabled ? 0.5 : 1) 31 | } 32 | } 33 | 34 | struct FullWidthGhostButtonContent: ViewModifier { 35 | var color: Color = Color.accentColor 36 | 37 | func body(content: Content) -> some View { 38 | content 39 | .frame(minWidth: 0, maxWidth: .infinity) 40 | .padding() 41 | .overlay( 42 | RoundedRectangle(cornerRadius: 40) 43 | .stroke(self.color, lineWidth: 1) 44 | ) 45 | .foregroundColor(self.color) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FingerspellingUITests/FingerspellingUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class FingerspellingUITests: XCTestCase { 4 | var app: XCUIApplication! 5 | 6 | override func setUp() { 7 | self.app = XCUIApplication() 8 | // Allows app to check if tests are running so we can mock randomness 9 | self.app.launchArguments.append("testing") 10 | setupSnapshot(self.app) 11 | self.app.launch() 12 | continueAfterFailure = false 13 | } 14 | 15 | func testReceptive() { 16 | snapshot("00Launch") 17 | self.app.buttons["Press \n to begin."].tap() 18 | snapshot("01Receptive") 19 | 20 | let instructions = self.app.buttons["Enter the word you saw."] 21 | self.waitForElement(instructions) 22 | 23 | self.app.typeText("turkey") 24 | snapshot("02Receptive") 25 | 26 | self.app.buttons["Done"].tap() 27 | self.waitForElement(self.app.staticTexts["TURKEY"]) 28 | } 29 | 30 | func testStatsReceptive() { 31 | self.app.buttons["28\n3"].tap() 32 | self.waitForElement(self.app.staticTexts["Stats (Receptive)"]) 33 | snapshot("03Stats") 34 | } 35 | 36 | func testExpressive() { 37 | self.app.buttons["line.horizontal.3\neyeglasses"].tap() 38 | self.app.buttons["Expressive"].tap() 39 | 40 | snapshot("05Expressive") 41 | 42 | self.app.buttons["Reveal"].tap() 43 | 44 | snapshot("06Expressive") 45 | 46 | self.app.buttons["Next word"].tap() 47 | 48 | self.app.buttons["line.horizontal.3\nhand.raised"].tap() 49 | snapshot("04Menu") 50 | } 51 | 52 | func testOpenCloseSettings() { 53 | self.app.buttons["gear"].tap() 54 | let settingsNavigationBar = self.app.navigationBars["Settings"] 55 | settingsNavigationBar.buttons["Done"].tap() 56 | } 57 | 58 | private func waitForElement(_ element: XCUIElement, timeout: TimeInterval = 10) { 59 | let existsPredicate = NSPredicate(format: "exists == true") 60 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil) 61 | waitForExpectations(timeout: timeout, handler: nil) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FingerspellingUITests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 2020.8 19 | CFBundleVersion 20 | 14 21 | 22 | 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | addressable (2.7.0) 6 | public_suffix (>= 2.0.2, < 5.0) 7 | atomos (0.1.3) 8 | aws-eventstream (1.0.3) 9 | aws-sdk (2.11.469) 10 | aws-sdk-resources (= 2.11.469) 11 | aws-sdk-core (2.11.469) 12 | aws-sigv4 (~> 1.0) 13 | jmespath (~> 1.0) 14 | aws-sdk-resources (2.11.469) 15 | aws-sdk-core (= 2.11.469) 16 | aws-sigv4 (1.1.1) 17 | aws-eventstream (~> 1.0, >= 1.0.2) 18 | babosa (1.0.3) 19 | claide (1.0.3) 20 | colored (1.2) 21 | colored2 (3.1.2) 22 | commander-fastlane (4.4.6) 23 | highline (~> 1.7.2) 24 | declarative (0.0.10) 25 | declarative-option (0.1.0) 26 | digest-crc (0.5.1) 27 | domain_name (0.5.20190701) 28 | unf (>= 0.0.5, < 1.0.0) 29 | dotenv (2.7.5) 30 | emoji_regex (1.0.1) 31 | excon (0.73.0) 32 | faraday (0.17.3) 33 | multipart-post (>= 1.2, < 3) 34 | faraday-cookie_jar (0.0.6) 35 | faraday (>= 0.7.4) 36 | http-cookie (~> 1.0.0) 37 | faraday_middleware (0.13.1) 38 | faraday (>= 0.7.4, < 1.0) 39 | fastimage (2.1.7) 40 | fastlane (2.143.0) 41 | CFPropertyList (>= 2.3, < 4.0.0) 42 | addressable (>= 2.3, < 3.0.0) 43 | aws-sdk (~> 2.3) 44 | babosa (>= 1.0.2, < 2.0.0) 45 | bundler (>= 1.12.0, < 3.0.0) 46 | colored 47 | commander-fastlane (>= 4.4.6, < 5.0.0) 48 | dotenv (>= 2.1.1, < 3.0.0) 49 | emoji_regex (>= 0.1, < 2.0) 50 | excon (>= 0.71.0, < 1.0.0) 51 | faraday (~> 0.17) 52 | faraday-cookie_jar (~> 0.0.6) 53 | faraday_middleware (~> 0.13.1) 54 | fastimage (>= 2.1.0, < 3.0.0) 55 | gh_inspector (>= 1.1.2, < 2.0.0) 56 | google-api-client (>= 0.29.2, < 0.37.0) 57 | google-cloud-storage (>= 1.15.0, < 2.0.0) 58 | highline (>= 1.7.2, < 2.0.0) 59 | json (< 3.0.0) 60 | jwt (~> 2.1.0) 61 | mini_magick (>= 4.9.4, < 5.0.0) 62 | multi_xml (~> 0.5) 63 | multipart-post (~> 2.0.0) 64 | plist (>= 3.1.0, < 4.0.0) 65 | public_suffix (~> 2.0.0) 66 | rubyzip (>= 1.3.0, < 2.0.0) 67 | security (= 0.1.3) 68 | simctl (~> 1.6.3) 69 | slack-notifier (>= 2.0.0, < 3.0.0) 70 | terminal-notifier (>= 2.0.0, < 3.0.0) 71 | terminal-table (>= 1.4.5, < 2.0.0) 72 | tty-screen (>= 0.6.3, < 1.0.0) 73 | tty-spinner (>= 0.8.0, < 1.0.0) 74 | word_wrap (~> 1.0.0) 75 | xcodeproj (>= 1.13.0, < 2.0.0) 76 | xcpretty (~> 0.3.0) 77 | xcpretty-travis-formatter (>= 0.0.3) 78 | gh_inspector (1.1.3) 79 | google-api-client (0.36.4) 80 | addressable (~> 2.5, >= 2.5.1) 81 | googleauth (~> 0.9) 82 | httpclient (>= 2.8.1, < 3.0) 83 | mini_mime (~> 1.0) 84 | representable (~> 3.0) 85 | retriable (>= 2.0, < 4.0) 86 | signet (~> 0.12) 87 | google-cloud-core (1.5.0) 88 | google-cloud-env (~> 1.0) 89 | google-cloud-errors (~> 1.0) 90 | google-cloud-env (1.3.1) 91 | faraday (>= 0.17.3, < 2.0) 92 | google-cloud-errors (1.0.0) 93 | google-cloud-storage (1.25.1) 94 | addressable (~> 2.5) 95 | digest-crc (~> 0.4) 96 | google-api-client (~> 0.33) 97 | google-cloud-core (~> 1.2) 98 | googleauth (~> 0.9) 99 | mini_mime (~> 1.0) 100 | googleauth (0.11.0) 101 | faraday (>= 0.17.3, < 2.0) 102 | jwt (>= 1.4, < 3.0) 103 | memoist (~> 0.16) 104 | multi_json (~> 1.11) 105 | os (>= 0.9, < 2.0) 106 | signet (~> 0.12) 107 | highline (1.7.10) 108 | http-cookie (1.0.3) 109 | domain_name (~> 0.5) 110 | httpclient (2.8.3) 111 | jmespath (1.4.0) 112 | json (2.3.0) 113 | jwt (2.1.0) 114 | memoist (0.16.2) 115 | mini_magick (4.10.1) 116 | mini_mime (1.0.2) 117 | multi_json (1.14.1) 118 | multi_xml (0.6.0) 119 | multipart-post (2.0.0) 120 | nanaimo (0.2.6) 121 | naturally (2.2.0) 122 | os (1.0.1) 123 | plist (3.5.0) 124 | public_suffix (2.0.5) 125 | representable (3.0.4) 126 | declarative (< 0.1.0) 127 | declarative-option (< 0.2.0) 128 | uber (< 0.2.0) 129 | retriable (3.1.2) 130 | rouge (2.0.7) 131 | rubyzip (1.3.0) 132 | security (0.1.3) 133 | signet (0.13.0) 134 | addressable (~> 2.3) 135 | faraday (>= 0.17.3, < 2.0) 136 | jwt (>= 1.5, < 3.0) 137 | multi_json (~> 1.10) 138 | simctl (1.6.8) 139 | CFPropertyList 140 | naturally 141 | slack-notifier (2.3.2) 142 | terminal-notifier (2.0.0) 143 | terminal-table (1.8.0) 144 | unicode-display_width (~> 1.1, >= 1.1.1) 145 | tty-cursor (0.7.1) 146 | tty-screen (0.7.1) 147 | tty-spinner (0.9.3) 148 | tty-cursor (~> 0.7) 149 | uber (0.1.0) 150 | unf (0.1.4) 151 | unf_ext 152 | unf_ext (0.0.7.6) 153 | unicode-display_width (1.7.0) 154 | word_wrap (1.0.0) 155 | xcodeproj (1.15.0) 156 | CFPropertyList (>= 2.3.3, < 4.0) 157 | atomos (~> 0.1.3) 158 | claide (>= 1.0.2, < 2.0) 159 | colored2 (~> 3.1) 160 | nanaimo (~> 0.2.6) 161 | xcpretty (0.3.0) 162 | rouge (~> 2.0.7) 163 | xcpretty-travis-formatter (1.0.0) 164 | xcpretty (~> 0.2, >= 0.0.7) 165 | 166 | PLATFORMS 167 | ruby 168 | 169 | DEPENDENCIES 170 | fastlane 171 | 172 | BUNDLED WITH 173 | 2.1.4 174 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | No data or personal information is collected by this app. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fingerspelling (iOS) 2 | 3 | iOS app to practice American Sign Language (ASL) fingerspelling. 4 | 5 | [![Available on the App Store](http://cl.ly/WouG/Download_on_the_App_Store_Badge_US-UK_135x40.svg)](https://apps.apple.com/us/app/asl-fingerspelling-practice/id1503242863) 6 | 7 |
8 | screenshot 9 |
10 | 11 | ## Feedback? Ideas? 12 | 13 | Open an [issue](https://github.com/sloria/Fingerspelling-iOS/issues)! 14 | 15 | ## License 16 | 17 | [MIT Licensed](https://sloria.mit-license.org/). 18 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Support 2 | 3 | Send questions and feedback to [sloria1+fingerspelling@gmail.com](mailto:sloria1+fingerspelling@gmail.com) or [open an issue](https://github.com/sloria/Fingerspelling-iOS/issues) on GitHub. All ideas and criticism are welcome. 4 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier("com.openasl.Fingerspelling") # The bundle identifier of your app 2 | # apple_id("[[APPLE_ID]]") # Your Apple email address 3 | 4 | 5 | # For more information about the Appfile, see: 6 | # https://docs.fastlane.tools/advanced/#appfile 7 | -------------------------------------------------------------------------------- /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 | default_platform(:ios) 17 | 18 | platform :ios do 19 | desc "Generate new localized screenshots" 20 | lane :screenshots do 21 | capture_screenshots(scheme: "FingerspellingUITests") 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios screenshots 20 | ``` 21 | fastlane ios screenshots 22 | ``` 23 | Generate new localized screenshots 24 | 25 | ---- 26 | 27 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 28 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 29 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 30 | -------------------------------------------------------------------------------- /fastlane/Snapfile: -------------------------------------------------------------------------------- 1 | devices([ 2 | # "iPhone 7", 3 | "iPhone 8 Plus", 4 | # "iPhone SE", 5 | # "iPhone Xs", 6 | "iPad Pro (12.9-inch) (4th generation)", 7 | # "iPad Pro (9.7-inch)", 8 | "iPhone 11 Pro Max" 9 | ]) 10 | 11 | languages([ 12 | "en-US", 13 | ]) 14 | 15 | clear_previous_screenshots(true) 16 | launch_arguments(["-testing YES"]) 17 | -------------------------------------------------------------------------------- /fastlane/SnapshotHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | var deviceLanguage = "" 5 | var locale = "" 6 | 7 | func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { 8 | Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations) 9 | } 10 | 11 | func snapshot(_ name: String, waitForLoadingIndicator: Bool) { 12 | if waitForLoadingIndicator { 13 | Snapshot.snapshot(name) 14 | } else { 15 | Snapshot.snapshot(name, timeWaitingForIdle: 0) 16 | } 17 | } 18 | 19 | /// - Parameters: 20 | /// - name: The name of the snapshot 21 | /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait. 22 | func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { 23 | Snapshot.snapshot(name, timeWaitingForIdle: timeout) 24 | } 25 | 26 | enum SnapshotError: Error, CustomDebugStringConvertible { 27 | case cannotDetectUser 28 | case cannotFindHomeDirectory 29 | case cannotFindSimulatorHomeDirectory 30 | case cannotAccessSimulatorHomeDirectory(String) 31 | case cannotRunOnPhysicalDevice 32 | 33 | var debugDescription: String { 34 | switch self { 35 | case .cannotDetectUser: 36 | return "Couldn't find Snapshot configuration files - can't detect current user " 37 | case .cannotFindHomeDirectory: 38 | return "Couldn't find Snapshot configuration files - can't detect `Users` dir" 39 | case .cannotFindSimulatorHomeDirectory: 40 | return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." 41 | case let .cannotAccessSimulatorHomeDirectory(simulatorHostHome): 42 | return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?" 43 | case .cannotRunOnPhysicalDevice: 44 | return "Can't use Snapshot on a physical device." 45 | } 46 | } 47 | } 48 | 49 | @objcMembers 50 | open class Snapshot: NSObject { 51 | static var app: XCUIApplication? 52 | static var waitForAnimations = true 53 | static var cacheDirectory: URL? 54 | static var screenshotsDirectory: URL? { 55 | cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true) 56 | } 57 | 58 | open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { 59 | Snapshot.app = app 60 | Snapshot.waitForAnimations = waitForAnimations 61 | 62 | do { 63 | let cacheDir = try pathPrefix() 64 | Snapshot.cacheDirectory = cacheDir 65 | self.setLanguage(app) 66 | self.setLocale(app) 67 | self.setLaunchArguments(app) 68 | } catch { 69 | NSLog(error.localizedDescription) 70 | } 71 | } 72 | 73 | class func setLanguage(_ app: XCUIApplication) { 74 | guard let cacheDirectory = self.cacheDirectory else { 75 | NSLog("CacheDirectory is not set - probably running on a physical device?") 76 | return 77 | } 78 | 79 | let path = cacheDirectory.appendingPathComponent("language.txt") 80 | 81 | do { 82 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines 83 | deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) 84 | app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] 85 | } catch { 86 | NSLog("Couldn't detect/set language...") 87 | } 88 | } 89 | 90 | class func setLocale(_ app: XCUIApplication) { 91 | guard let cacheDirectory = self.cacheDirectory else { 92 | NSLog("CacheDirectory is not set - probably running on a physical device?") 93 | return 94 | } 95 | 96 | let path = cacheDirectory.appendingPathComponent("locale.txt") 97 | 98 | do { 99 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines 100 | locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) 101 | } catch { 102 | NSLog("Couldn't detect/set locale...") 103 | } 104 | 105 | if locale.isEmpty, !deviceLanguage.isEmpty { 106 | locale = Locale(identifier: deviceLanguage).identifier 107 | } 108 | 109 | if !locale.isEmpty { 110 | app.launchArguments += ["-AppleLocale", "\"\(locale)\""] 111 | } 112 | } 113 | 114 | class func setLaunchArguments(_ app: XCUIApplication) { 115 | guard let cacheDirectory = self.cacheDirectory else { 116 | NSLog("CacheDirectory is not set - probably running on a physical device?") 117 | return 118 | } 119 | 120 | let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt") 121 | app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] 122 | 123 | do { 124 | let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8) 125 | let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) 126 | let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count)) 127 | let results = matches.map { result -> String in 128 | (launchArguments as NSString).substring(with: result.range) 129 | } 130 | app.launchArguments += results 131 | } catch { 132 | NSLog("Couldn't detect/set launch_arguments...") 133 | } 134 | } 135 | 136 | open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { 137 | if timeout > 0 { 138 | self.waitForLoadingIndicatorToDisappear(within: timeout) 139 | } 140 | 141 | NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work 142 | 143 | if Snapshot.waitForAnimations { 144 | sleep(1) // Waiting for the animation to be finished (kind of) 145 | } 146 | 147 | #if os(OSX) 148 | guard let app = self.app else { 149 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 150 | return 151 | } 152 | 153 | app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) 154 | #else 155 | 156 | guard self.app != nil else { 157 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 158 | return 159 | } 160 | 161 | let screenshot = XCUIScreen.main.screenshot() 162 | guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } 163 | 164 | do { 165 | // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices 166 | let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") 167 | let range = NSRange(location: 0, length: simulator.count) 168 | simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") 169 | 170 | let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") 171 | try screenshot.pngRepresentation.write(to: path) 172 | } catch { 173 | NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") 174 | NSLog(error.localizedDescription) 175 | } 176 | #endif 177 | } 178 | 179 | class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) { 180 | #if os(tvOS) 181 | return 182 | #endif 183 | 184 | guard let app = self.app else { 185 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 186 | return 187 | } 188 | 189 | let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element 190 | let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator) 191 | _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout) 192 | } 193 | 194 | class func pathPrefix() throws -> URL? { 195 | let homeDir: URL 196 | // on OSX config is stored in /Users//Library 197 | // and on iOS/tvOS/WatchOS it's in simulator's home dir 198 | #if os(OSX) 199 | guard let user = ProcessInfo().environment["USER"] else { 200 | throw SnapshotError.cannotDetectUser 201 | } 202 | 203 | guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else { 204 | throw SnapshotError.cannotFindHomeDirectory 205 | } 206 | 207 | homeDir = usersDir.appendingPathComponent(user) 208 | #else 209 | #if arch(i386) || arch(x86_64) 210 | guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { 211 | throw SnapshotError.cannotFindSimulatorHomeDirectory 212 | } 213 | guard let homeDirUrl = URL(string: simulatorHostHome) else { 214 | throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome) 215 | } 216 | homeDir = URL(fileURLWithPath: homeDirUrl.path) 217 | #else 218 | throw SnapshotError.cannotRunOnPhysicalDevice 219 | #endif 220 | #endif 221 | return homeDir.appendingPathComponent("Library/Caches/tools.fastlane") 222 | } 223 | } 224 | 225 | private extension XCUIElementAttributes { 226 | var isNetworkLoadingIndicator: Bool { 227 | if self.hasWhiteListedIdentifier { return false } 228 | 229 | let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20) 230 | let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3) 231 | 232 | return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize 233 | } 234 | 235 | var hasWhiteListedIdentifier: Bool { 236 | let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] 237 | 238 | return whiteListedIdentifiers.contains(identifier) 239 | } 240 | 241 | func isStatusBar(_ deviceWidth: CGFloat) -> Bool { 242 | if elementType == .statusBar { return true } 243 | guard frame.origin == .zero else { return false } 244 | 245 | let oldStatusBarSize = CGSize(width: deviceWidth, height: 20) 246 | let newStatusBarSize = CGSize(width: deviceWidth, height: 44) 247 | 248 | return [oldStatusBarSize, newStatusBarSize].contains(frame.size) 249 | } 250 | } 251 | 252 | private extension XCUIElementQuery { 253 | var networkLoadingIndicators: XCUIElementQuery { 254 | let isNetworkLoadingIndicator = NSPredicate { evaluatedObject, _ in 255 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false } 256 | 257 | return element.isNetworkLoadingIndicator 258 | } 259 | 260 | return self.containing(isNetworkLoadingIndicator) 261 | } 262 | 263 | var deviceStatusBars: XCUIElementQuery { 264 | guard let app = Snapshot.app else { 265 | fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 266 | } 267 | 268 | let deviceWidth = app.windows.firstMatch.frame.width 269 | 270 | let isStatusBar = NSPredicate { evaluatedObject, _ in 271 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false } 272 | 273 | return element.isStatusBar(deviceWidth) 274 | } 275 | 276 | return self.containing(isStatusBar) 277 | } 278 | } 279 | 280 | private extension CGFloat { 281 | func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool { 282 | numberA ... numberB ~= self 283 | } 284 | } 285 | 286 | // Please don't remove the lines below 287 | // They are used to detect outdated configuration files 288 | // SnapshotHelperVersion [1.21] 289 | -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/media/screenshot.png -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | ## `test.sh` 4 | 5 | Runs tests. 6 | 7 | ``` 8 | ./scripts/test.sh 9 | ``` 10 | 11 | 12 | ## `gen_words.sh` 13 | 14 | Generates Words.swift file with the words lists. 15 | 16 | List of high-frequency words comes from https://github.com/derekchuank/high-frequency-vocabulary/blob/master/10k.txt 17 | List of naughty words comes from https://github.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/blob/master/en 18 | 19 | To generate words file: 20 | 21 | ``` 22 | ./scripts/gen_words.sh 23 | ``` 24 | -------------------------------------------------------------------------------- /scripts/blacklist.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenASL/Fingerspelling-iOS/64f62460a630f9918e98669b66a7d347c05002a2/scripts/blacklist.db -------------------------------------------------------------------------------- /scripts/bump.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | marketing_version=$1 5 | 6 | xcrun agvtool next-version -all 7 | xcrun agvtool new-marketing-version $marketing_version 8 | 9 | build=$(xcrun agvtool vers -terse) 10 | 11 | git add . 12 | git commit -m "chore: bump version and build number" 13 | git tag "$marketing_version-$build" 14 | 15 | echo "Done." 16 | -------------------------------------------------------------------------------- /scripts/gen_words.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pathlib 3 | import sqlite3 4 | import urllib.request 5 | 6 | HERE = pathlib.Path(__file__).parent 7 | OUTPUT = HERE.parent / "Fingerspelling" / "Data" / "Words.swift" 8 | BLACKLIST = HERE / "blacklist.db" 9 | MIN_LENGTH = 3 10 | MAX_LENGTH = 12 11 | 12 | WORDS_URL = "https://raw.githubusercontent.com/derekchuank/high-frequency-vocabulary/master/10k.txt" 13 | NAUGHTY_WORDS_URL = "https://raw.githubusercontent.com/sloria/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/en/en" 14 | TEMPLATE = """ 15 | /** 16 | GENERATED FILE--DO NOT EDIT 17 | */ 18 | public var AllWords: [String] = {words} 19 | public var Words: [String] = AllWords 20 | """ 21 | 22 | 23 | def fetch(url): 24 | req = urllib.request.Request(url) 25 | return urllib.request.urlopen(req) 26 | 27 | 28 | def main(): 29 | con = sqlite3.connect("scripts/blacklist.db") 30 | 31 | blacklisted_words = { 32 | row[0] for row in con.execute("select word from blacklisted_words") 33 | } 34 | print("Fetching words lists...") 35 | naughty_resp = fetch(NAUGHTY_WORDS_URL) 36 | for line in naughty_resp.readlines(): 37 | word = line.decode("utf-8").lower().strip() 38 | # very naive inflection, but fine for out purposes 39 | plural, past, gerund = f"{word}s", f"{word}ed", f"{word}ing" 40 | blacklisted_words |= {word, plural, past, gerund} 41 | 42 | words_resp = fetch(WORDS_URL) 43 | words = [] 44 | for line in words_resp.readlines(): 45 | word = line.decode("utf-8").lower().strip() 46 | if MAX_LENGTH >= len(word) >= MIN_LENGTH and word not in blacklisted_words: 47 | words.append(word) 48 | 49 | print(f"Writing {len(words)} words to {OUTPUT}...") 50 | content = TEMPLATE.format(words=json.dumps(words)) 51 | with OUTPUT.open("w") as out_fp: 52 | out_fp.write(content) 53 | print("Done.") 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /scripts/gen_words.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | python scripts/gen_words.py 5 | 6 | git add . 7 | git commit -m "chore: regenerate words list" 8 | 9 | echo "Done." 10 | -------------------------------------------------------------------------------- /scripts/screenshots.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | fastlane snapshot 5 | cp fastlane/screenshots/en-US/iPhone\ 11\ Pro\ Max-01Receptive.png media/screenshot.png 6 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | destination=${1:-"platform=iOS Simulator,OS=13.3,name=iPhone 11"} 5 | xcodebuild clean test -project Fingerspelling.xcodeproj -scheme FingerspellingUITests -destination "$destination" 6 | --------------------------------------------------------------------------------