diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..916c7c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +xcuserdata/ + diff --git a/SampleApp.xcodeproj/project.pbxproj b/Etymo.xcodeproj/project.pbxproj similarity index 73% rename from SampleApp.xcodeproj/project.pbxproj rename to Etymo.xcodeproj/project.pbxproj index f58e994..da98950 100644 --- a/SampleApp.xcodeproj/project.pbxproj +++ b/Etymo.xcodeproj/project.pbxproj @@ -7,23 +7,24 @@ objects = { /* Begin PBXBuildFile section */ - 06A897FA286BEBA500F773B5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A897F9286BEBA500F773B5 /* AppDelegate.swift */; }; - 06A897FC286BEBA500F773B5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A897FB286BEBA500F773B5 /* SceneDelegate.swift */; }; - 06A897FE286BEBA500F773B5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A897FD286BEBA500F773B5 /* ViewController.swift */; }; - 06A89801286BEBA500F773B5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 06A897FF286BEBA500F773B5 /* Main.storyboard */; }; 06A89803286BEBA600F773B5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 06A89802286BEBA600F773B5 /* Assets.xcassets */; }; 06A89806286BEBA600F773B5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 06A89804286BEBA600F773B5 /* LaunchScreen.storyboard */; }; - 06A89811286BEBA600F773B5 /* SampleAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89810286BEBA600F773B5 /* SampleAppTests.swift */; }; - 06A8981B286BEBA600F773B5 /* SampleAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8981A286BEBA600F773B5 /* SampleAppUITests.swift */; }; - 06A8981D286BEBA600F773B5 /* SampleAppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8981C286BEBA600F773B5 /* SampleAppUITestsLaunchTests.swift */; }; + 06A89811286BEBA600F773B5 /* EtymoAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89810286BEBA600F773B5 /* EtymoAppTests.swift */; }; + 06A8981B286BEBA600F773B5 /* EtymoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8981A286BEBA600F773B5 /* EtymoUITests.swift */; }; + 06A8981D286BEBA600F773B5 /* EtymoUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8981C286BEBA600F773B5 /* EtymoUITestsLaunchTests.swift */; }; 06A8982A286BEECA00F773B5 /* Tokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89829286BEECA00F773B5 /* Tokens.swift */; }; 06A8982C286BF00800F773B5 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8982B286BF00800F773B5 /* API.swift */; }; 06A8982E286BF07B00F773B5 /* Word.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8982D286BF07B00F773B5 /* Word.swift */; }; 06A89832286BF3D200F773B5 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89831286BF3D200F773B5 /* APIError.swift */; }; 06A89836286C071200F773B5 /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89835286C071200F773B5 /* URLBuilder.swift */; }; - 06A89838286C0FFC00F773B5 /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89837286C0FFC00F773B5 /* TableViewDataSource.swift */; }; 06A8983A286C165A00F773B5 /* WordResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A89839286C165A00F773B5 /* WordResponse.swift */; }; 06A8983C286C168800F773B5 /* Meta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A8983B286C168800F773B5 /* Meta.swift */; }; + D9EFB1952C7B3B61008DF3A4 /* EtymoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EFB1942C7B3B61008DF3A4 /* EtymoApp.swift */; }; + D9EFB1972C7B3BA0008DF3A4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EFB1962C7B3B9D008DF3A4 /* ContentView.swift */; }; + D9EFB19C2C7B4F62008DF3A4 /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EFB19B2C7B4F5A008DF3A4 /* ContentViewModel.swift */; }; + D9EFB19E2C7B57CC008DF3A4 /* ContentUnavailableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EFB19D2C7B57CC008DF3A4 /* ContentUnavailableView.swift */; }; + D9EFB1A02C7B59BA008DF3A4 /* WordItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EFB19F2C7B59B8008DF3A4 /* WordItemView.swift */; }; + D9EFB1A42C7B9FE5008DF3A4 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D9EFB1A32C7B9FE5008DF3A4 /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,27 +45,30 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 06A897F6286BEBA500F773B5 /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 06A897F9286BEBA500F773B5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 06A897FB286BEBA500F773B5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 06A897FD286BEBA500F773B5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 06A89800286BEBA500F773B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 06A897F6286BEBA500F773B5 /* Etymo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Etymo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 06A89802286BEBA600F773B5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 06A89805286BEBA600F773B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 06A89807286BEBA600F773B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 06A8980C286BEBA600F773B5 /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 06A89810286BEBA600F773B5 /* SampleAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleAppTests.swift; sourceTree = ""; }; - 06A89816286BEBA600F773B5 /* SampleAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 06A8981A286BEBA600F773B5 /* SampleAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleAppUITests.swift; sourceTree = ""; }; - 06A8981C286BEBA600F773B5 /* SampleAppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleAppUITestsLaunchTests.swift; sourceTree = ""; }; + 06A8980C286BEBA600F773B5 /* EtymoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EtymoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 06A89810286BEBA600F773B5 /* EtymoAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtymoAppTests.swift; sourceTree = ""; }; + 06A89816286BEBA600F773B5 /* EtymoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EtymoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 06A8981A286BEBA600F773B5 /* EtymoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtymoUITests.swift; sourceTree = ""; }; + 06A8981C286BEBA600F773B5 /* EtymoUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtymoUITestsLaunchTests.swift; sourceTree = ""; }; 06A89829286BEECA00F773B5 /* Tokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tokens.swift; sourceTree = ""; }; 06A8982B286BF00800F773B5 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 06A8982D286BF07B00F773B5 /* Word.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Word.swift; sourceTree = ""; }; 06A89831286BF3D200F773B5 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; 06A89835286C071200F773B5 /* URLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilder.swift; sourceTree = ""; }; - 06A89837286C0FFC00F773B5 /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = ""; }; 06A89839286C165A00F773B5 /* WordResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordResponse.swift; sourceTree = ""; }; 06A8983B286C168800F773B5 /* Meta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Meta.swift; sourceTree = ""; }; + D93453102C7AF426004ABF8C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + D9EFB1922C7B0B08008DF3A4 /* WhatToTest.en-US.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "WhatToTest.en-US.txt"; sourceTree = ""; }; + D9EFB1942C7B3B61008DF3A4 /* EtymoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtymoApp.swift; sourceTree = ""; }; + D9EFB1962C7B3B9D008DF3A4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + D9EFB19B2C7B4F5A008DF3A4 /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = ""; }; + D9EFB19D2C7B57CC008DF3A4 /* ContentUnavailableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentUnavailableView.swift; sourceTree = ""; }; + D9EFB19F2C7B59B8008DF3A4 /* WordItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordItemView.swift; sourceTree = ""; }; + D9EFB1A32C7B9FE5008DF3A4 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -116,9 +120,11 @@ 06A897ED286BEBA500F773B5 = { isa = PBXGroup; children = ( - 06A897F8286BEBA500F773B5 /* SampleApp */, - 06A8980F286BEBA600F773B5 /* SampleAppTests */, - 06A89819286BEBA600F773B5 /* SampleAppUITests */, + D93453102C7AF426004ABF8C /* README.md */, + D9EFB1932C7B19F5008DF3A4 /* TestFlight */, + 06A897F8286BEBA500F773B5 /* Etymo */, + 06A8980F286BEBA600F773B5 /* EtymoTests */, + 06A89819286BEBA600F773B5 /* EtymoUITests */, 06A897F7286BEBA500F773B5 /* Products */, ); sourceTree = ""; @@ -126,53 +132,62 @@ 06A897F7286BEBA500F773B5 /* Products */ = { isa = PBXGroup; children = ( - 06A897F6286BEBA500F773B5 /* SampleApp.app */, - 06A8980C286BEBA600F773B5 /* SampleAppTests.xctest */, - 06A89816286BEBA600F773B5 /* SampleAppUITests.xctest */, + 06A897F6286BEBA500F773B5 /* Etymo.app */, + 06A8980C286BEBA600F773B5 /* EtymoTests.xctest */, + 06A89816286BEBA600F773B5 /* EtymoUITests.xctest */, ); name = Products; sourceTree = ""; }; - 06A897F8286BEBA500F773B5 /* SampleApp */ = { + 06A897F8286BEBA500F773B5 /* Etymo */ = { isa = PBXGroup; children = ( - 06A897F9286BEBA500F773B5 /* AppDelegate.swift */, - 06A897FB286BEBA500F773B5 /* SceneDelegate.swift */, - 06A897FD286BEBA500F773B5 /* ViewController.swift */, - 06A897FF286BEBA500F773B5 /* Main.storyboard */, - 06A89837286C0FFC00F773B5 /* TableViewDataSource.swift */, + D9EFB1942C7B3B61008DF3A4 /* EtymoApp.swift */, + D9EFB1962C7B3B9D008DF3A4 /* ContentView.swift */, + D9EFB19B2C7B4F5A008DF3A4 /* ContentViewModel.swift */, + D9EFB19D2C7B57CC008DF3A4 /* ContentUnavailableView.swift */, + D9EFB19F2C7B59B8008DF3A4 /* WordItemView.swift */, 062E352E286CC5950072B38E /* API */, 062E352F286CC5B10072B38E /* Model */, 06A89802286BEBA600F773B5 /* Assets.xcassets */, 06A89804286BEBA600F773B5 /* LaunchScreen.storyboard */, 06A89807286BEBA600F773B5 /* Info.plist */, + D9EFB1A32C7B9FE5008DF3A4 /* Localizable.xcstrings */, ); - path = SampleApp; + path = Etymo; sourceTree = ""; }; - 06A8980F286BEBA600F773B5 /* SampleAppTests */ = { + 06A8980F286BEBA600F773B5 /* EtymoTests */ = { isa = PBXGroup; children = ( - 06A89810286BEBA600F773B5 /* SampleAppTests.swift */, + 06A89810286BEBA600F773B5 /* EtymoAppTests.swift */, ); - path = SampleAppTests; + path = EtymoTests; sourceTree = ""; }; - 06A89819286BEBA600F773B5 /* SampleAppUITests */ = { + 06A89819286BEBA600F773B5 /* EtymoUITests */ = { isa = PBXGroup; children = ( - 06A8981A286BEBA600F773B5 /* SampleAppUITests.swift */, - 06A8981C286BEBA600F773B5 /* SampleAppUITestsLaunchTests.swift */, + 06A8981A286BEBA600F773B5 /* EtymoUITests.swift */, + 06A8981C286BEBA600F773B5 /* EtymoUITestsLaunchTests.swift */, ); - path = SampleAppUITests; + path = EtymoUITests; + sourceTree = ""; + }; + D9EFB1932C7B19F5008DF3A4 /* TestFlight */ = { + isa = PBXGroup; + children = ( + D9EFB1922C7B0B08008DF3A4 /* WhatToTest.en-US.txt */, + ); + path = TestFlight; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 06A897F5286BEBA500F773B5 /* SampleApp */ = { + 06A897F5286BEBA500F773B5 /* Etymo */ = { isa = PBXNativeTarget; - buildConfigurationList = 06A89820286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "SampleApp" */; + buildConfigurationList = 06A89820286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "Etymo" */; buildPhases = ( 06A897F2286BEBA500F773B5 /* Sources */, 06A897F3286BEBA500F773B5 /* Frameworks */, @@ -182,14 +197,14 @@ ); dependencies = ( ); - name = SampleApp; + name = Etymo; productName = SampleApp; - productReference = 06A897F6286BEBA500F773B5 /* SampleApp.app */; + productReference = 06A897F6286BEBA500F773B5 /* Etymo.app */; productType = "com.apple.product-type.application"; }; - 06A8980B286BEBA600F773B5 /* SampleAppTests */ = { + 06A8980B286BEBA600F773B5 /* EtymoTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 06A89823286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "SampleAppTests" */; + buildConfigurationList = 06A89823286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "EtymoTests" */; buildPhases = ( 06A89808286BEBA600F773B5 /* Sources */, 06A89809286BEBA600F773B5 /* Frameworks */, @@ -200,14 +215,14 @@ dependencies = ( 06A8980E286BEBA600F773B5 /* PBXTargetDependency */, ); - name = SampleAppTests; + name = EtymoTests; productName = SampleAppTests; - productReference = 06A8980C286BEBA600F773B5 /* SampleAppTests.xctest */; + productReference = 06A8980C286BEBA600F773B5 /* EtymoTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 06A89815286BEBA600F773B5 /* SampleAppUITests */ = { + 06A89815286BEBA600F773B5 /* EtymoUITests */ = { isa = PBXNativeTarget; - buildConfigurationList = 06A89826286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "SampleAppUITests" */; + buildConfigurationList = 06A89826286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "EtymoUITests" */; buildPhases = ( 06A89812286BEBA600F773B5 /* Sources */, 06A89813286BEBA600F773B5 /* Frameworks */, @@ -218,9 +233,9 @@ dependencies = ( 06A89818286BEBA600F773B5 /* PBXTargetDependency */, ); - name = SampleAppUITests; + name = EtymoUITests; productName = SampleAppUITests; - productReference = 06A89816286BEBA600F773B5 /* SampleAppUITests.xctest */; + productReference = 06A89816286BEBA600F773B5 /* EtymoUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ @@ -246,22 +261,27 @@ }; }; }; - buildConfigurationList = 06A897F1286BEBA500F773B5 /* Build configuration list for PBXProject "SampleApp" */; + buildConfigurationList = 06A897F1286BEBA500F773B5 /* Build configuration list for PBXProject "Etymo" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, + es, + fr, + de, + it, + ja, ); mainGroup = 06A897ED286BEBA500F773B5; productRefGroup = 06A897F7286BEBA500F773B5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 06A897F5286BEBA500F773B5 /* SampleApp */, - 06A8980B286BEBA600F773B5 /* SampleAppTests */, - 06A89815286BEBA600F773B5 /* SampleAppUITests */, + 06A897F5286BEBA500F773B5 /* Etymo */, + 06A8980B286BEBA600F773B5 /* EtymoTests */, + 06A89815286BEBA600F773B5 /* EtymoUITests */, ); }; /* End PBXProject section */ @@ -273,7 +293,7 @@ files = ( 06A89806286BEBA600F773B5 /* LaunchScreen.storyboard in Resources */, 06A89803286BEBA600F773B5 /* Assets.xcassets in Resources */, - 06A89801286BEBA500F773B5 /* Main.storyboard in Resources */, + D9EFB1A42C7B9FE5008DF3A4 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -299,15 +319,16 @@ buildActionMask = 2147483647; files = ( 06A8983A286C165A00F773B5 /* WordResponse.swift in Sources */, - 06A897FE286BEBA500F773B5 /* ViewController.swift in Sources */, - 06A897FA286BEBA500F773B5 /* AppDelegate.swift in Sources */, + D9EFB1972C7B3BA0008DF3A4 /* ContentView.swift in Sources */, 06A89836286C071200F773B5 /* URLBuilder.swift in Sources */, 06A8983C286C168800F773B5 /* Meta.swift in Sources */, + D9EFB19E2C7B57CC008DF3A4 /* ContentUnavailableView.swift in Sources */, + D9EFB19C2C7B4F62008DF3A4 /* ContentViewModel.swift in Sources */, 06A8982E286BF07B00F773B5 /* Word.swift in Sources */, - 06A897FC286BEBA500F773B5 /* SceneDelegate.swift in Sources */, 06A89832286BF3D200F773B5 /* APIError.swift in Sources */, + D9EFB1A02C7B59BA008DF3A4 /* WordItemView.swift in Sources */, 06A8982A286BEECA00F773B5 /* Tokens.swift in Sources */, - 06A89838286C0FFC00F773B5 /* TableViewDataSource.swift in Sources */, + D9EFB1952C7B3B61008DF3A4 /* EtymoApp.swift in Sources */, 06A8982C286BF00800F773B5 /* API.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -316,7 +337,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 06A89811286BEBA600F773B5 /* SampleAppTests.swift in Sources */, + 06A89811286BEBA600F773B5 /* EtymoAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -324,8 +345,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 06A8981D286BEBA600F773B5 /* SampleAppUITestsLaunchTests.swift in Sources */, - 06A8981B286BEBA600F773B5 /* SampleAppUITests.swift in Sources */, + 06A8981D286BEBA600F773B5 /* EtymoUITestsLaunchTests.swift in Sources */, + 06A8981B286BEBA600F773B5 /* EtymoUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -334,25 +355,17 @@ /* Begin PBXTargetDependency section */ 06A8980E286BEBA600F773B5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 06A897F5286BEBA500F773B5 /* SampleApp */; + target = 06A897F5286BEBA500F773B5 /* Etymo */; targetProxy = 06A8980D286BEBA600F773B5 /* PBXContainerItemProxy */; }; 06A89818286BEBA600F773B5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 06A897F5286BEBA500F773B5 /* SampleApp */; + target = 06A897F5286BEBA500F773B5 /* Etymo */; targetProxy = 06A89817286BEBA600F773B5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 06A897FF286BEBA500F773B5 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 06A89800286BEBA500F773B5 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; 06A89804286BEBA600F773B5 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -420,6 +433,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -473,6 +487,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; @@ -485,12 +500,11 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UT7JE36P34; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = SampleApp/Info.plist; + INFOPLIST_FILE = Etymo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -498,7 +512,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Pura.scents.SampleApp; + PRODUCT_BUNDLE_IDENTIFIER = app.etymo.Etymo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -513,12 +527,11 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UT7JE36P34; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = SampleApp/Info.plist; + INFOPLIST_FILE = Etymo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -526,7 +539,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Pura.scents.SampleApp; + PRODUCT_BUNDLE_IDENTIFIER = app.etymo.Etymo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -541,16 +554,16 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 3XU8MWPJTL; + DEVELOPMENT_TEAM = UT7JE36P34; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.5; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Pura.scents.SampleAppTests; + PRODUCT_BUNDLE_IDENTIFIER = app.etymo.EtymoTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Etymo.app/Etymo"; }; name = Debug; }; @@ -561,16 +574,16 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 3XU8MWPJTL; + DEVELOPMENT_TEAM = UT7JE36P34; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.5; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Pura.scents.SampleAppTests; + PRODUCT_BUNDLE_IDENTIFIER = app.etymo.EtymoTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Etymo.app/Etymo"; }; name = Release; }; @@ -580,15 +593,15 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 3XU8MWPJTL; + DEVELOPMENT_TEAM = UT7JE36P34; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Pura.scents.SampleAppUITests; + PRODUCT_BUNDLE_IDENTIFIER = app.etymo.EtymoUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = SampleApp; + TEST_TARGET_NAME = Etymo; }; name = Debug; }; @@ -598,22 +611,22 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 3XU8MWPJTL; + DEVELOPMENT_TEAM = UT7JE36P34; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Pura.scents.SampleAppUITests; + PRODUCT_BUNDLE_IDENTIFIER = app.etymo.EtymoUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = SampleApp; + TEST_TARGET_NAME = Etymo; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 06A897F1286BEBA500F773B5 /* Build configuration list for PBXProject "SampleApp" */ = { + 06A897F1286BEBA500F773B5 /* Build configuration list for PBXProject "Etymo" */ = { isa = XCConfigurationList; buildConfigurations = ( 06A8981E286BEBA600F773B5 /* Debug */, @@ -622,7 +635,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 06A89820286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "SampleApp" */ = { + 06A89820286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "Etymo" */ = { isa = XCConfigurationList; buildConfigurations = ( 06A89821286BEBA600F773B5 /* Debug */, @@ -631,7 +644,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 06A89823286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "SampleAppTests" */ = { + 06A89823286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "EtymoTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 06A89824286BEBA600F773B5 /* Debug */, @@ -640,7 +653,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 06A89826286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "SampleAppUITests" */ = { + 06A89826286BEBA600F773B5 /* Build configuration list for PBXNativeTarget "EtymoUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 06A89827286BEBA600F773B5 /* Debug */, diff --git a/SampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Etymo.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from SampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Etymo.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/SampleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Etymo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from SampleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Etymo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Etymo.xcodeproj/xcshareddata/xcschemes/Etymo.xcscheme b/Etymo.xcodeproj/xcshareddata/xcschemes/Etymo.xcscheme new file mode 100644 index 0000000..f231a3c --- /dev/null +++ b/Etymo.xcodeproj/xcshareddata/xcschemes/Etymo.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleApp/API/API.swift b/Etymo/API/API.swift similarity index 99% rename from SampleApp/API/API.swift rename to Etymo/API/API.swift index 72193de..b256c60 100644 --- a/SampleApp/API/API.swift +++ b/Etymo/API/API.swift @@ -1,6 +1,6 @@ // // API.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/SampleApp/API/APIError.swift b/Etymo/API/APIError.swift similarity index 93% rename from SampleApp/API/APIError.swift rename to Etymo/API/APIError.swift index 8a79e7d..afb0447 100644 --- a/SampleApp/API/APIError.swift +++ b/Etymo/API/APIError.swift @@ -1,6 +1,6 @@ // // APIError.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/SampleApp/API/Tokens.swift b/Etymo/API/Tokens.swift similarity index 94% rename from SampleApp/API/Tokens.swift rename to Etymo/API/Tokens.swift index a0515af..eefe708 100644 --- a/SampleApp/API/Tokens.swift +++ b/Etymo/API/Tokens.swift @@ -1,6 +1,6 @@ // // Tokens.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/SampleApp/API/URLBuilder.swift b/Etymo/API/URLBuilder.swift similarity index 95% rename from SampleApp/API/URLBuilder.swift rename to Etymo/API/URLBuilder.swift index 3571320..fe97637 100644 --- a/SampleApp/API/URLBuilder.swift +++ b/Etymo/API/URLBuilder.swift @@ -1,6 +1,6 @@ // // URLBuilder.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/Etymo/Assets.xcassets/AccentColor.colorset/Contents.json b/Etymo/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..00b4b78 --- /dev/null +++ b/Etymo/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x22", + "green" : "0x22", + "red" : "0x24" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0xC8", + "green" : "0xE7", + "red" : "0xFA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Etymo/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/Etymo/Assets.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000..4bdd4b0 Binary files /dev/null and b/Etymo/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/Etymo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Etymo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..cefcc87 --- /dev/null +++ b/Etymo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SampleApp/Assets.xcassets/Contents.json b/Etymo/Assets.xcassets/Contents.json similarity index 100% rename from SampleApp/Assets.xcassets/Contents.json rename to Etymo/Assets.xcassets/Contents.json diff --git a/Etymo/Assets.xcassets/Symbols/Contents.json b/Etymo/Assets.xcassets/Symbols/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Etymo/Assets.xcassets/Symbols/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Etymo/Assets.xcassets/Symbols/text.page.badge.magnifyingglass.symbolset/Contents.json b/Etymo/Assets.xcassets/Symbols/text.page.badge.magnifyingglass.symbolset/Contents.json new file mode 100644 index 0000000..31a31db --- /dev/null +++ b/Etymo/Assets.xcassets/Symbols/text.page.badge.magnifyingglass.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "text.page.badge.magnifyingglass.svg", + "idiom" : "universal" + } + ] +} diff --git a/Etymo/Assets.xcassets/Symbols/text.page.badge.magnifyingglass.symbolset/text.page.badge.magnifyingglass.svg b/Etymo/Assets.xcassets/Symbols/text.page.badge.magnifyingglass.symbolset/text.page.badge.magnifyingglass.svg new file mode 100644 index 0000000..a3e5b8e --- /dev/null +++ b/Etymo/Assets.xcassets/Symbols/text.page.badge.magnifyingglass.symbolset/text.page.badge.magnifyingglass.svg @@ -0,0 +1,125 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.5.0 + Requires Xcode 15 or greater + Generated from text.page.badge.magnifyingglass + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleApp/Assets.xcassets/AccentColor.colorset/Contents.json b/Etymo/Assets.xcassets/Symbols/wifi.slash.symbolset/Contents.json similarity index 61% rename from SampleApp/Assets.xcassets/AccentColor.colorset/Contents.json rename to Etymo/Assets.xcassets/Symbols/wifi.slash.symbolset/Contents.json index eb87897..d11e618 100644 --- a/SampleApp/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Etymo/Assets.xcassets/Symbols/wifi.slash.symbolset/Contents.json @@ -1,11 +1,12 @@ { - "colors" : [ - { - "idiom" : "universal" - } - ], "info" : { "author" : "xcode", "version" : 1 - } + }, + "symbols" : [ + { + "filename" : "wifi.slash.svg", + "idiom" : "universal" + } + ] } diff --git a/Etymo/Assets.xcassets/Symbols/wifi.slash.symbolset/wifi.slash.svg b/Etymo/Assets.xcassets/Symbols/wifi.slash.symbolset/wifi.slash.svg new file mode 100644 index 0000000..da7411f --- /dev/null +++ b/Etymo/Assets.xcassets/Symbols/wifi.slash.symbolset/wifi.slash.svg @@ -0,0 +1,125 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.5.0 + Requires Xcode 15 or greater + Generated from wifi.slash + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleApp/Base.lproj/LaunchScreen.storyboard b/Etymo/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from SampleApp/Base.lproj/LaunchScreen.storyboard rename to Etymo/Base.lproj/LaunchScreen.storyboard diff --git a/Etymo/ContentUnavailableView.swift b/Etymo/ContentUnavailableView.swift new file mode 100644 index 0000000..ca7b4b9 --- /dev/null +++ b/Etymo/ContentUnavailableView.swift @@ -0,0 +1,36 @@ +import SwiftUI + +struct ContentUnavailableView: View { + var image: Image + var title: LocalizedStringKey + var description: LocalizedStringKey + + var body: some View { + VStack(spacing: 8) { + image + .font(.largeTitle.weight(.bold)) + .imageScale(.large) + .foregroundStyle(.tint) + .accessibilityHidden(true) + + VStack(spacing: 4) { + Text(title) + .font(.headline) + Text(description) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .multilineTextAlignment(.center) + .accessibilityElement(children: .combine) + } + } +} + +#Preview { + ContentUnavailableView( + image: Image(.wifiSlash), + title: "Not Connected", + description: "Unable to lookup words while offline.\nCheck your Internet connection." + ) + .tint(.red) +} diff --git a/Etymo/ContentView.swift b/Etymo/ContentView.swift new file mode 100644 index 0000000..fe70daa --- /dev/null +++ b/Etymo/ContentView.swift @@ -0,0 +1,74 @@ +import SwiftUI + +struct ContentView: View { + @StateObject private var viewModel = ContentViewModel() + + var body: some View { + NavigationView { + GeometryReader { geometry in + ScrollView { + VStack { + if viewModel.isLoading { + ProgressView() + } else if let word = viewModel.word { + WordItemView(word: word) + .frame(maxWidth: .infinity) + .padding() + } else if viewModel.searchText.isEmpty { + emptySearchView + .padding(.bottom, 48) + .frame(maxHeight: .infinity, alignment: .center) + } else { + noResultsView + .padding(.bottom, 48) + .frame(maxHeight: .infinity, alignment: .center) + } + } + .frame(width: geometry.size.width) + .frame(minHeight: geometry.size.height) + } + .navigationTitle("Etymo") + .searchable(text: $viewModel.searchText, prompt: "Word") + } + } + .overlay { + if !viewModel.isConnected { + notConnectedView + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(.background) + } + } + } + + var emptySearchView: some View { + ContentUnavailableView( + image: Image(.textPageBadgeMagnifyingglass), + title: "Enter a Word", + description: "Type a word into the search to see its definitions." + ) + .tint(.secondary) + } + + var noResultsView: some View { + ContentUnavailableView( + image: Image(.textPageBadgeMagnifyingglass), + title: "No Results", + description: "Check the spelling or try a new search." + ) + .tint(.secondary) + } + + var notConnectedView: some View { + ContentUnavailableView( + image: Image(.wifiSlash), + title: "Not Connected", + description: "Unable to lookup words while offline.\nCheck your Internet connection." + ) + .tint(.red) + } +} + +#Preview { + ContentView() +} diff --git a/Etymo/ContentViewModel.swift b/Etymo/ContentViewModel.swift new file mode 100644 index 0000000..6574c2e --- /dev/null +++ b/Etymo/ContentViewModel.swift @@ -0,0 +1,55 @@ +import Combine +import Network +import SwiftUI + +class ContentViewModel: ObservableObject { + @Published var searchText = "" + @Published var word: Word? + @Published var isLoading = false + @Published var isConnected = true + + private let networkMonitor = NWPathMonitor() + private let queue = DispatchQueue(label: "NetworkMonitor") + private var cancellables = Set() + + init() { + $searchText + .debounce(for: .milliseconds(500), scheduler: RunLoop.main) + .removeDuplicates() + .sink { [weak self] searchText in + self?.fetchWord(query: searchText) + } + .store(in: &cancellables) + + networkMonitor.pathUpdateHandler = { path in + DispatchQueue.main.async { + self.isConnected = path.status == .satisfied + } + } + networkMonitor.start(queue: queue) + } + + private func fetchWord(query: String) { + isLoading = true + + API.shared.fetchWord(query: query) { response in + switch response { + case .success(let data): + guard let r = WordResponse.parseData(data) else { + return + } + + DispatchQueue.main.async { + self.word = r.word + self.isLoading = false + } + case .failure(let error): + DispatchQueue.main.async { + self.word = nil + self.isLoading = false + } + print("NETWORK ERROR: ", error.localizedDescription) + } + } + } +} diff --git a/Etymo/EtymoApp.swift b/Etymo/EtymoApp.swift new file mode 100644 index 0000000..61422fe --- /dev/null +++ b/Etymo/EtymoApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct EtymoApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/SampleApp.xcodeproj/xcuserdata/natehancock.xcuserdatad/xcschemes/xcschememanagement.plist b/Etymo/Info.plist similarity index 57% rename from SampleApp.xcodeproj/xcuserdata/natehancock.xcuserdatad/xcschemes/xcschememanagement.plist rename to Etymo/Info.plist index 0ea061c..669a5ca 100644 --- a/SampleApp.xcodeproj/xcuserdata/natehancock.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Etymo/Info.plist @@ -2,13 +2,12 @@ - SchemeUserState + ITSAppUsesNonExemptEncryption + + UIApplicationSceneManifest - SampleApp.xcscheme_^#shared#^_ - - orderHint - 0 - + UIApplicationSupportsMultipleScenes + diff --git a/Etymo/Localizable.xcstrings b/Etymo/Localizable.xcstrings new file mode 100644 index 0000000..a9bde53 --- /dev/null +++ b/Etymo/Localizable.xcstrings @@ -0,0 +1,312 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "%lld. %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld. %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld. %@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld. %@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld. %@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld. %@" + } + } + } + }, + "Check the spelling or try a new search." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Überprüfen Sie die Rechtschreibung oder versuchen Sie eine neue Suche." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verifica la ortografía o intenta una nueva búsqueda." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vérifiez l'orthographe ou essayez une nouvelle recherche." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Controlla l'ortografia o prova una nuova ricerca." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スペルを確認するか、新しい検索を試してください。" + } + } + } + }, + "Enter a Word" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geben Sie ein Wort ein" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingresa una Palabra" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entrez un Mot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inserisci una Parola" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "単語を入力してください" + } + } + } + }, + "Etymo" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etymo" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etimo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Étymo" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etimo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エティモ" + } + } + } + }, + "No Results" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Ergebnisse" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin Resultados" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun Résultat" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun Risultato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "結果がありません" + } + } + } + }, + "Not Connected" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nicht Verbunden" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Conectado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pas Connecté" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non Connesso" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続されていません" + } + } + } + }, + "Type a word into the search to see its definitions." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geben Sie ein Wort in die Suche ein, um dessen Definitionen zu sehen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escribe una palabra en la búsqueda para ver sus definiciones." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tapez un mot dans la recherche pour voir ses définitions." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Digita una parola nella ricerca per vedere le sue definizioni." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "単語を検索ボックスに入力して、定義を確認してください。" + } + } + } + }, + "Unable to lookup words while offline.\nCheck your Internet connection." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wörter können offline nicht nachgeschlagen werden. Überprüfen Sie Ihre Internetverbindung." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pueden buscar palabras sin conexión. Verifica tu conexión a Internet." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de rechercher des mots hors ligne. Vérifiez votre connexion Internet." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile cercare le parole offline. Controlla la tua connessione Internet." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "オフラインでは単語を検索できません。インターネット接続を確認してください。" + } + } + } + }, + "Word" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wort" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Palabra" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "単語" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/SampleApp/Model/Meta.swift b/Etymo/Model/Meta.swift similarity index 93% rename from SampleApp/Model/Meta.swift rename to Etymo/Model/Meta.swift index 0f4b881..d11ff45 100644 --- a/SampleApp/Model/Meta.swift +++ b/Etymo/Model/Meta.swift @@ -1,6 +1,6 @@ // // Meta.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/SampleApp/Model/Word.swift b/Etymo/Model/Word.swift similarity index 98% rename from SampleApp/Model/Word.swift rename to Etymo/Model/Word.swift index dd72e19..e792b27 100644 --- a/SampleApp/Model/Word.swift +++ b/Etymo/Model/Word.swift @@ -1,6 +1,6 @@ // // Word.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/SampleApp/Model/WordResponse.swift b/Etymo/Model/WordResponse.swift similarity index 97% rename from SampleApp/Model/WordResponse.swift rename to Etymo/Model/WordResponse.swift index 391594a..56a0845 100644 --- a/SampleApp/Model/WordResponse.swift +++ b/Etymo/Model/WordResponse.swift @@ -1,6 +1,6 @@ // // WordResponse.swift -// SampleApp +// Etymo // // Created by natehancock on 6/28/22. // diff --git a/Etymo/WordItemView.swift b/Etymo/WordItemView.swift new file mode 100644 index 0000000..245381c --- /dev/null +++ b/Etymo/WordItemView.swift @@ -0,0 +1,34 @@ +import SwiftUI + +struct WordItemView: View { + let word: Word + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text(word.text) + .font(.title) + + VStack(alignment: .leading, spacing: 8) { + ForEach(Array(word.definitions.enumerated()), id: \.offset) { index, definition in + Text("\(index + 1). \(definition)") + .font(.subheadline) + } + } + } + .padding() + .background(.primary.opacity(0.1), in: .rect(cornerRadius: 8)) + } +} + +#Preview { + WordItemView( + word: .init( + text: "serendipity", + definitions: [ + "the faculty or phenomenon of finding valuable or agreeable things not sought for; also : an instance of this" + ] + ) + ) + .frame(maxWidth: .infinity) + .padding() +} diff --git a/SampleAppTests/SampleAppTests.swift b/EtymoTests/EtymoAppTests.swift similarity index 91% rename from SampleAppTests/SampleAppTests.swift rename to EtymoTests/EtymoAppTests.swift index 8906db8..10569ce 100644 --- a/SampleAppTests/SampleAppTests.swift +++ b/EtymoTests/EtymoAppTests.swift @@ -1,14 +1,14 @@ // -// SampleAppTests.swift -// SampleAppTests +// EtymoTests.swift +// EtymoTests // // Created by natehancock on 6/28/22. // import XCTest -@testable import SampleApp +@testable import Etymo -class SampleAppTests: XCTestCase { +class EtymoAppTests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. diff --git a/SampleAppUITests/SampleAppUITests.swift b/EtymoUITests/EtymoUITests.swift similarity index 93% rename from SampleAppUITests/SampleAppUITests.swift rename to EtymoUITests/EtymoUITests.swift index 0dfeb4d..035361b 100644 --- a/SampleAppUITests/SampleAppUITests.swift +++ b/EtymoUITests/EtymoUITests.swift @@ -1,13 +1,13 @@ // -// SampleAppUITests.swift -// SampleAppUITests +// EtymoUITests.swift +// EtymoUITests // // Created by natehancock on 6/28/22. // import XCTest -class SampleAppUITests: XCTestCase { +class EtymoUITests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. diff --git a/SampleAppUITests/SampleAppUITestsLaunchTests.swift b/EtymoUITests/EtymoUITestsLaunchTests.swift similarity index 86% rename from SampleAppUITests/SampleAppUITestsLaunchTests.swift rename to EtymoUITests/EtymoUITestsLaunchTests.swift index f6e00ef..06159fe 100644 --- a/SampleAppUITests/SampleAppUITestsLaunchTests.swift +++ b/EtymoUITests/EtymoUITestsLaunchTests.swift @@ -1,13 +1,13 @@ // -// SampleAppUITestsLaunchTests.swift -// SampleAppUITests +// EtymoUITestsLaunchTests.swift +// EtymoUITests // // Created by natehancock on 6/28/22. // import XCTest -class SampleAppUITestsLaunchTests: XCTestCase { +class EtymoUITestsLaunchTests: XCTestCase { override class var runsForEachTargetApplicationUIConfiguration: Bool { true diff --git a/README.md b/README.md index b58a92d..f0ba247 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,40 @@ -# Hello +# Etymo -Welcome to the Pura Interview Process. Thank you for taking the time and we look forward to talking with you more. +Explore words like never before with definitions, origins, usage, and more in one powerful app. -### Task -We would like you to take this sample app and improve it in some way. The project is open ended but feel free to follow any #suggestions +## Development -Please take 2-4 hours to plan and make your improvements. Please fork this into your own public repository, make your changes, and submit a PR in your repo with your changes. (this prevents other candidates from easily viewing your changes) Then share the repository for review. +Open `Etymo.xcodeproj` and build/run the project. -### Purpose of Task -We want to see how you interact with an existing codebase. +### Images -A few things we will consider: -- Code Style, Quality, and Understandability -- Does it work? +Convert all icons to [symbols](https://developer.apple.com/documentation/uikit/uiimage/creating_custom_symbol_images_for_your_app) and store them in `Assets.xcassets/Symbols`. Export symbols from [SF Symbols](https://developer.apple.com/sf-symbols/) or use [SF Symbol Creator](https://www.figma.com/community/plugin/1207724751253683840) to export custom icons from Figma. +Use compiled assets instead of string named images: -Be prepared to talk about which improvements you made and why you made them in the next interview. +``` +// Incorrect +Image("star") -Feel free to reach out with any questions. nateh@pura.com +// Correct +Image(.star) +``` +### Device Support -### App -This is a simple app where you can type in a word and get definitions for that word +Support is provided for the most current version and the previous version of iOS and iPadOS, as well as for earlier versions until their usage falls below 10%. For the latest statistics, see [iOS and iPadOS Usage](https://developer.apple.com/support/app-store/). -### API +## Deployment -We are using a public api provided by Merriam Webster. +Update `TestFlight/WhatToTest.en-US.txt` with any instructions for testers. -The Dictionary API is found [here](https://dictionaryapi.com/products/api-collegiate-dictionary) +Push to the `interview-submission` branch and the project will auto-deploy to [TestFlight](https://testflight.apple.com/join/9t9cTXZ2) using Xcode Cloud. -and Documentation can be found [here](https://dictionaryapi.com/products/json) +To release the app, use [App Store Connect](https://appstoreconnect.apple.com) to create a new version and submit it for Apple Review. -### Suggestions -- Improve the user experience -- Add Views and experience For Thesaurus. `Tokens.apiKeyThes` -- Unit Tests or UI Tests -- view for empty state -- Error handling and display Errors to user -- Refactor to SwiftUI -- Refactor to Combine -- Add an easter egg or something to make us laugh - - Giphy of the searched word - - Konami Code +## Resources +- 🖥️ [Etymo Website](https://etymo.app) +- 🎨 [Figma Designs](https://www.figma.com/design/Edqid3ps1vdztSLTrzo5Fq/Etymo?node-id=0-1&t=Vs8GlF9mtC4CZoUX-1) +- 📖 [Merriam Webster Dictionary API](https://dictionaryapi.com/products/api-collegiate-dictionary) +- 📖 [Merriam Webster Documentation](https://dictionaryapi.com/products/json) diff --git a/SampleApp/AppDelegate.swift b/SampleApp/AppDelegate.swift deleted file mode 100644 index bb95b47..0000000 --- a/SampleApp/AppDelegate.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// AppDelegate.swift -// SampleApp -// -// Created by natehancock on 6/28/22. -// - -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - // MARK: UISceneSession Lifecycle - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - - -} - diff --git a/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 5a3257a..0000000 --- a/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/SampleApp/Base.lproj/Main.storyboard b/SampleApp/Base.lproj/Main.storyboard deleted file mode 100644 index fff8a42..0000000 --- a/SampleApp/Base.lproj/Main.storyboard +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SampleApp/Info.plist b/SampleApp/Info.plist deleted file mode 100644 index dd3c9af..0000000 --- a/SampleApp/Info.plist +++ /dev/null @@ -1,25 +0,0 @@ - - - - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - - - diff --git a/SampleApp/SceneDelegate.swift b/SampleApp/SceneDelegate.swift deleted file mode 100644 index e59ccb2..0000000 --- a/SampleApp/SceneDelegate.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// SceneDelegate.swift -// SampleApp -// -// Created by natehancock on 6/28/22. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - -} - diff --git a/SampleApp/TableViewDataSource.swift b/SampleApp/TableViewDataSource.swift deleted file mode 100644 index c8ddd60..0000000 --- a/SampleApp/TableViewDataSource.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// TableViewDataSource.swift -// SampleApp -// -// Created by natehancock on 6/28/22. -// - -import Foundation -import UIKit - - -class TableViewDataSource: NSObject { - - enum State { - case empty - case word(Word) - } - - var state: State - init(state: State) { - self.state = state - } - - func updateState(_ state: State, completion: @escaping () -> ()) { - self.state = state - DispatchQueue.main.async { - completion() - } - } -} - -extension TableViewDataSource: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard case let State.word(word) = state else { - return 0 - } - return word.definitions.count + 1 - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard case let State.word(word) = state else { - return UITableViewCell() - } - let cell = UITableViewCell(style: .value1, reuseIdentifier: "cell") - cell.selectionStyle = .none - - if indexPath.row == 0 { - cell.textLabel?.text = "word:" - cell.detailTextLabel?.text = word.text - } else { - cell.textLabel?.text = "definition:" - cell.detailTextLabel?.text = word.definitions[indexPath.row - 1] - } - return cell - } -} diff --git a/SampleApp/ViewController.swift b/SampleApp/ViewController.swift deleted file mode 100644 index a94407f..0000000 --- a/SampleApp/ViewController.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// ViewController.swift -// SampleApp -// -// Created by natehancock on 6/28/22. -// - -import UIKit - -class ViewController: UIViewController { - - var dataSource = TableViewDataSource(state: .empty) - - @IBOutlet weak var textField: UITextField! - @IBOutlet weak var tableView: UITableView! - - - @IBAction func didTapButton() { - guard let text = textField.text else { - return - } - - API.shared.fetchWord(query: text) { response in - switch response { - case .success(let data): - guard let r = WordResponse.parseData(data) else { - return - } - - self.dataSource.updateState(.word(r.word)) { - self.tableView.reloadData() - } - - case .failure(let error): - self.dataSource.updateState(.empty) { - self.tableView.reloadData() - } - print("NETWORK ERROR: ", error.localizedDescription) - } - } - } - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - - tableView.dataSource = dataSource - tableView.delegate = self - - tableView.rowHeight = UITableView.automaticDimension - } - - -} - -extension ViewController: UITableViewDelegate { - -} diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt new file mode 100644 index 0000000..316502e --- /dev/null +++ b/TestFlight/WhatToTest.en-US.txt @@ -0,0 +1 @@ +Look up a word to get its definition.