Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 430ae744cc | |||
| 98d565ba0d |
+90
@@ -0,0 +1,90 @@
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
|
||||
build/
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
# *.xcodeproj
|
||||
#
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
# .swiftpm
|
||||
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# Accio dependency management
|
||||
Dependencies/
|
||||
.accio/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
#
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
@@ -3,28 +3,34 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 52;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
9B024EA225A6E76E000BE823 /* DefaultHomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B024EA125A6E76E000BE823 /* DefaultHomeCoordinator.swift */; };
|
||||
9B024EA325A6E76E000BE823 /* DefaultHomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B024EA125A6E76E000BE823 /* DefaultHomeCoordinator.swift */; };
|
||||
9B024EA925A6E78A000BE823 /* DefaultRecipeListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B024EA825A6E78A000BE823 /* DefaultRecipeListCoordinator.swift */; };
|
||||
9B024EAA25A6E78A000BE823 /* DefaultRecipeListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B024EA825A6E78A000BE823 /* DefaultRecipeListCoordinator.swift */; };
|
||||
9B0BC35B25A34D9600C018D3 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0BC35A25A34D9600C018D3 /* SafariView.swift */; };
|
||||
9B0BC36525A394E100C018D3 /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B4FCF9225A2CFC5006BE60E /* AsyncImage.swift */; };
|
||||
9B0BC36A25A394E400C018D3 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0BC35A25A34D9600C018D3 /* SafariView.swift */; };
|
||||
9B0BC36F25A394E600C018D3 /* RatingStars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D00A9259FE1BE00684D22 /* RatingStars.swift */; };
|
||||
9B0BC37425A3952700C018D3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D0097259FDFF100684D22 /* SettingsView.swift */; };
|
||||
9B4FCF9425A2CFC5006BE60E /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B4FCF9225A2CFC5006BE60E /* AsyncImage.swift */; };
|
||||
9B692CAF25A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B692CAE25A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift */; };
|
||||
9B692CB025A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B692CAE25A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift */; };
|
||||
9B692CB625A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B692CB525A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift */; };
|
||||
9B692CB725A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B692CB525A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift */; };
|
||||
9B692CBD25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B692CBC25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift */; };
|
||||
9B692CBE25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B692CBC25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift */; };
|
||||
9B77A4DE25ED208400EBDCD6 /* XUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9B77A4DD25ED208400EBDCD6 /* XUI */; };
|
||||
9B77A4E425ED208E00EBDCD6 /* XUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9B77A4E325ED208E00EBDCD6 /* XUI */; };
|
||||
9B8D0098259FDFF100684D22 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D0097259FDFF100684D22 /* SettingsView.swift */; };
|
||||
9B8D00AA259FE1BE00684D22 /* RatingStars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8D00A9259FE1BE00684D22 /* RatingStars.swift */; };
|
||||
9BB59DC925A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DC825A4BDB600946BFB /* RecipeListCoordinatorView.swift */; };
|
||||
9BB59DCA25A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DC825A4BDB600946BFB /* RecipeListCoordinatorView.swift */; };
|
||||
9BB59DD425A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DD325A4BDF100946BFB /* RecipeListCoordinator.swift */; };
|
||||
9BB59DD525A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DD325A4BDF100946BFB /* RecipeListCoordinator.swift */; };
|
||||
9BB59DF825A4C19500946BFB /* SheetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DF725A4C19500946BFB /* SheetModifier.swift */; };
|
||||
9BB59DF925A4C19500946BFB /* SheetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DF725A4C19500946BFB /* SheetModifier.swift */; };
|
||||
9BB59DFF25A4C1A000946BFB /* PopoverModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DFE25A4C1A000946BFB /* PopoverModifier.swift */; };
|
||||
9BB59E0025A4C1A000946BFB /* PopoverModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59DFE25A4C1A000946BFB /* PopoverModifier.swift */; };
|
||||
9BB59E0625A4C32200946BFB /* View+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E0525A4C32200946BFB /* View+Navigation.swift */; };
|
||||
9BB59E0725A4C32200946BFB /* View+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E0525A4C32200946BFB /* View+Navigation.swift */; };
|
||||
9BB59E1E25A4C5BF00946BFB /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E1D25A4C5BF00946BFB /* URL+Identifiable.swift */; };
|
||||
9BB59E1F25A4C5BF00946BFB /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB59E1D25A4C5BF00946BFB /* URL+Identifiable.swift */; };
|
||||
9BE362A62583CD1F00807BFC /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE362A52583CD1F00807BFC /* Tests_iOS.swift */; };
|
||||
@@ -73,15 +79,17 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9B024EA125A6E76E000BE823 /* DefaultHomeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultHomeCoordinator.swift; sourceTree = "<group>"; };
|
||||
9B024EA825A6E78A000BE823 /* DefaultRecipeListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRecipeListCoordinator.swift; sourceTree = "<group>"; };
|
||||
9B0BC35A25A34D9600C018D3 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||
9B4FCF9225A2CFC5006BE60E /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = "<group>"; };
|
||||
9B692CAE25A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRecipeListViewModel.swift; sourceTree = "<group>"; };
|
||||
9B692CB525A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRecipeViewModel.swift; sourceTree = "<group>"; };
|
||||
9B692CBC25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRatingViewModel.swift; sourceTree = "<group>"; };
|
||||
9B8D0097259FDFF100684D22 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
9B8D00A9259FE1BE00684D22 /* RatingStars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingStars.swift; sourceTree = "<group>"; };
|
||||
9BB59DC825A4BDB600946BFB /* RecipeListCoordinatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeListCoordinatorView.swift; sourceTree = "<group>"; };
|
||||
9BB59DD325A4BDF100946BFB /* RecipeListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeListCoordinator.swift; sourceTree = "<group>"; };
|
||||
9BB59DF725A4C19500946BFB /* SheetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetModifier.swift; sourceTree = "<group>"; };
|
||||
9BB59DFE25A4C1A000946BFB /* PopoverModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverModifier.swift; sourceTree = "<group>"; };
|
||||
9BB59E0525A4C32200946BFB /* View+Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Navigation.swift"; sourceTree = "<group>"; };
|
||||
9BB59E1D25A4C5BF00946BFB /* URL+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Identifiable.swift"; sourceTree = "<group>"; };
|
||||
9BE3628A2583CD1700807BFC /* RecipesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipesApp.swift; sourceTree = "<group>"; };
|
||||
9BE3628C2583CD1E00807BFC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -113,6 +121,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9B77A4DE25ED208400EBDCD6 /* XUI in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -120,6 +129,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9B77A4E425ED208E00EBDCD6 /* XUI in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -144,11 +154,19 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9BE3633A25841BA200807BFC /* RecipeListViewModel.swift */,
|
||||
9B692CAE25A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift */,
|
||||
9BE3630A2583CFCF00807BFC /* RecipeList.swift */,
|
||||
);
|
||||
path = RecipeList;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9B6D639C25A6B6EE006A458E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9B8D0096259FDFE200684D22 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -171,21 +189,12 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9BB59DD325A4BDF100946BFB /* RecipeListCoordinator.swift */,
|
||||
9B024EA825A6E78A000BE823 /* DefaultRecipeListCoordinator.swift */,
|
||||
9BB59DC825A4BDB600946BFB /* RecipeListCoordinatorView.swift */,
|
||||
);
|
||||
path = RecipeListCoordinator;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9BB59E0C25A4C44100946BFB /* ViewModifiers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9BB59DF725A4C19500946BFB /* SheetModifier.swift */,
|
||||
9BB59DFE25A4C1A000946BFB /* PopoverModifier.swift */,
|
||||
9BB59E0525A4C32200946BFB /* View+Navigation.swift */,
|
||||
);
|
||||
path = ViewModifiers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9BB59E1C25A4C5AE00946BFB /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -203,6 +212,7 @@
|
||||
9BE362A42583CD1F00807BFC /* Tests iOS */,
|
||||
9BE362AF2583CD1F00807BFC /* Tests macOS */,
|
||||
9BE362922583CD1E00807BFC /* Products */,
|
||||
9B6D639C25A6B6EE006A458E /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -212,7 +222,6 @@
|
||||
9BE3628A2583CD1700807BFC /* RecipesApp.swift */,
|
||||
9BE362F92583CF4100807BFC /* Scenes */,
|
||||
9B8D00A8259FE1AF00684D22 /* Views */,
|
||||
9BB59E0C25A4C44100946BFB /* ViewModifiers */,
|
||||
9BB59E1C25A4C5AE00946BFB /* Extensions */,
|
||||
9BE362FE2583CF4B00807BFC /* Model */,
|
||||
9BE3628C2583CD1E00807BFC /* Assets.xcassets */,
|
||||
@@ -270,6 +279,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9BE362D22583CDB300807BFC /* HomeCoordinator.swift */,
|
||||
9B024EA125A6E76E000BE823 /* DefaultHomeCoordinator.swift */,
|
||||
9BE363032583CFC400807BFC /* HomeCoordinatorView.swift */,
|
||||
);
|
||||
path = Home;
|
||||
@@ -279,6 +289,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9BE362D92583CE0200807BFC /* RecipeViewModel.swift */,
|
||||
9B692CB525A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift */,
|
||||
9BE363202583D19600807BFC /* RecipeView.swift */,
|
||||
);
|
||||
path = Recipe;
|
||||
@@ -288,6 +299,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9BE362E02583CE2C00807BFC /* RatingViewModel.swift */,
|
||||
9B692CBC25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift */,
|
||||
9BE363272583D1A200807BFC /* RatingView.swift */,
|
||||
);
|
||||
path = Rating;
|
||||
@@ -331,6 +343,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = "Recipes (iOS)";
|
||||
packageProductDependencies = (
|
||||
9B77A4DD25ED208400EBDCD6 /* XUI */,
|
||||
);
|
||||
productName = "Recipes (iOS)";
|
||||
productReference = 9BE362912583CD1E00807BFC /* Recipes.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -348,6 +363,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = "Recipes (macOS)";
|
||||
packageProductDependencies = (
|
||||
9B77A4E325ED208E00EBDCD6 /* XUI */,
|
||||
);
|
||||
productName = "Recipes (macOS)";
|
||||
productReference = 9BE362992583CD1E00807BFC /* Recipes.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -395,7 +413,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1220;
|
||||
LastUpgradeCheck = 1220;
|
||||
LastUpgradeCheck = 1240;
|
||||
TargetAttributes = {
|
||||
9BE362902583CD1E00807BFC = {
|
||||
CreatedOnToolsVersion = 12.2;
|
||||
@@ -422,6 +440,9 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 9BE362842583CD1600807BFC;
|
||||
packageReferences = (
|
||||
9B77A4DC25ED208400EBDCD6 /* XCRemoteSwiftPackageReference "XUI" */,
|
||||
);
|
||||
productRefGroup = 9BE362922583CD1E00807BFC /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -479,19 +500,21 @@
|
||||
9BE362CC2583CD3000807BFC /* Recipe.swift in Sources */,
|
||||
9BE362E82583CE9400807BFC /* RecipeService.swift in Sources */,
|
||||
9B8D00AA259FE1BE00684D22 /* RatingStars.swift in Sources */,
|
||||
9BB59DFF25A4C1A000946BFB /* PopoverModifier.swift in Sources */,
|
||||
9B024EA225A6E76E000BE823 /* DefaultHomeCoordinator.swift in Sources */,
|
||||
9BE363282583D1A200807BFC /* RatingView.swift in Sources */,
|
||||
9BB59E0625A4C32200946BFB /* View+Navigation.swift in Sources */,
|
||||
9BE362B32583CD1F00807BFC /* RecipesApp.swift in Sources */,
|
||||
9BE362E12583CE2C00807BFC /* RatingViewModel.swift in Sources */,
|
||||
9BB59DC925A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */,
|
||||
9B692CB625A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift in Sources */,
|
||||
9BE362DA2583CE0200807BFC /* RecipeViewModel.swift in Sources */,
|
||||
9BB59DD425A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */,
|
||||
9BE363042583CFC400807BFC /* HomeCoordinatorView.swift in Sources */,
|
||||
9B0BC35B25A34D9600C018D3 /* SafariView.swift in Sources */,
|
||||
9BB59DF825A4C19500946BFB /* SheetModifier.swift in Sources */,
|
||||
9B8D0098259FDFF100684D22 /* SettingsView.swift in Sources */,
|
||||
9B692CBD25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift in Sources */,
|
||||
9BE362D32583CDB300807BFC /* HomeCoordinator.swift in Sources */,
|
||||
9B692CAF25A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift in Sources */,
|
||||
9B024EA925A6E78A000BE823 /* DefaultRecipeListCoordinator.swift in Sources */,
|
||||
9BE3630B2583CFCF00807BFC /* RecipeList.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -507,19 +530,21 @@
|
||||
9BE362E92583CE9400807BFC /* RecipeService.swift in Sources */,
|
||||
9BE363292583D1A200807BFC /* RatingView.swift in Sources */,
|
||||
9BE362B42583CD1F00807BFC /* RecipesApp.swift in Sources */,
|
||||
9BB59E0025A4C1A000946BFB /* PopoverModifier.swift in Sources */,
|
||||
9B024EA325A6E76E000BE823 /* DefaultHomeCoordinator.swift in Sources */,
|
||||
9B0BC36A25A394E400C018D3 /* SafariView.swift in Sources */,
|
||||
9BB59E0725A4C32200946BFB /* View+Navigation.swift in Sources */,
|
||||
9BE362E22583CE2C00807BFC /* RatingViewModel.swift in Sources */,
|
||||
9B0BC37425A3952700C018D3 /* SettingsView.swift in Sources */,
|
||||
9BB59DCA25A4BDB600946BFB /* RecipeListCoordinatorView.swift in Sources */,
|
||||
9B692CB725A6E7CF008D7FB9 /* DefaultRecipeViewModel.swift in Sources */,
|
||||
9BE362DB2583CE0200807BFC /* RecipeViewModel.swift in Sources */,
|
||||
9BB59DD525A4BDF100946BFB /* RecipeListCoordinator.swift in Sources */,
|
||||
9BE363052583CFC400807BFC /* HomeCoordinatorView.swift in Sources */,
|
||||
9BE362D42583CDB300807BFC /* HomeCoordinator.swift in Sources */,
|
||||
9BB59DF925A4C19500946BFB /* SheetModifier.swift in Sources */,
|
||||
9B0BC36525A394E100C018D3 /* AsyncImage.swift in Sources */,
|
||||
9B692CBE25A6E7E5008D7FB9 /* DefaultRatingViewModel.swift in Sources */,
|
||||
9B0BC36F25A394E600C018D3 /* RatingStars.swift in Sources */,
|
||||
9B692CB025A6E7BB008D7FB9 /* DefaultRecipeListViewModel.swift in Sources */,
|
||||
9B024EAA25A6E78A000BE823 /* DefaultRecipeListCoordinator.swift in Sources */,
|
||||
9BE3630C2583CFCF00807BFC /* RecipeList.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -718,6 +743,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 77E79NGPCV;
|
||||
@@ -742,6 +768,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 77E79NGPCV;
|
||||
@@ -898,6 +925,30 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
9B77A4DC25ED208400EBDCD6 /* XCRemoteSwiftPackageReference "XUI" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/quickbirdstudios/XUI";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
9B77A4DD25ED208400EBDCD6 /* XUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9B77A4DC25ED208400EBDCD6 /* XCRemoteSwiftPackageReference "XUI" */;
|
||||
productName = XUI;
|
||||
};
|
||||
9B77A4E325ED208E00EBDCD6 /* XUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9B77A4DC25ED208400EBDCD6 /* XCRemoteSwiftPackageReference "XUI" */;
|
||||
productName = XUI;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 9BE362852583CD1600807BFC /* Project object */;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "XUI",
|
||||
"repositoryURL": "https://github.com/quickbirdstudios/XUI",
|
||||
"state": {
|
||||
"branch": "main",
|
||||
"revision": "6bfaf4412d7fa0081a81650dc1551e6670a5c375",
|
||||
"version": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
BIN
Binary file not shown.
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Recipes (iOS).xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>Recipes (macOS).xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -117,6 +117,12 @@ class RecipeService {
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func fetchRecipe(id: String, _ completion: @escaping (Recipe?) -> Void) {
|
||||
fetchRecipes { recipes in
|
||||
completion(recipes.first { $0.id.uuidString == id })
|
||||
}
|
||||
}
|
||||
|
||||
func fetchRecipes(_ completion: @escaping ([Recipe]) -> Void) {
|
||||
completion(
|
||||
Mirror(reflecting: self)
|
||||
|
||||
@@ -12,14 +12,46 @@ struct RecipesApp: App {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@StateObject var coordinator = HomeCoordinator(recipeService: RecipeService())
|
||||
let recipeService: RecipeService
|
||||
@StateObject var coordinator: DefaultHomeCoordinator
|
||||
@State var hasOpenedURL = false
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init() {
|
||||
recipeService = RecipeService()
|
||||
let coordinator = DefaultHomeCoordinator(recipeService: recipeService)
|
||||
_coordinator = .init(wrappedValue: coordinator)
|
||||
}
|
||||
|
||||
// MARK: Scenes
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
HomeCoordinatorView(coordinator: coordinator)
|
||||
.onOpenURL { coordinator.startDeepLink(from: $0) }
|
||||
.onAppear { simulateURLOpening() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
private func simulateURLOpening() {
|
||||
#if DEBUG
|
||||
guard !hasOpenedURL else {
|
||||
return
|
||||
}
|
||||
hasOpenedURL = true
|
||||
self.recipeService.fetchRecipes { recipes in
|
||||
guard let recipe = recipes.randomElement(),
|
||||
let url = URL(string: "recipes://ratings?recipeID=" + recipe.id.uuidString) else {
|
||||
assertionFailure("Could not find recipe or illegal url format.")
|
||||
return
|
||||
}
|
||||
|
||||
coordinator.startDeepLink(from: url)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// DefaultHomeCoordinator.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 07.01.21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XUI
|
||||
|
||||
class DefaultHomeCoordinator: ObservableObject, HomeCoordinator {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published var tab = HomeTab.meat
|
||||
@Published private(set) var veggieCoordinator: RecipeListCoordinator!
|
||||
@Published private(set) var meatCoordinator: RecipeListCoordinator!
|
||||
@Published var openedURL: URL?
|
||||
|
||||
private let recipeService: RecipeService
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(recipeService: RecipeService) {
|
||||
self.recipeService = recipeService
|
||||
|
||||
self.veggieCoordinator = DefaultRecipeListCoordinator(
|
||||
title: "Veggie",
|
||||
recipeService: recipeService,
|
||||
parent: self,
|
||||
filter: { $0.isVegetarian }
|
||||
)
|
||||
|
||||
self.meatCoordinator = DefaultRecipeListCoordinator(
|
||||
title: "Meat",
|
||||
recipeService: recipeService,
|
||||
parent: self,
|
||||
filter: { !$0.isVegetarian }
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func startDeepLink(from url: URL) {
|
||||
guard url.scheme == "recipes",
|
||||
url.host == "ratings",
|
||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
let recipeID = components.queryItems?.first(where: { $0.name == "recipeID" })?.value else {
|
||||
assertionFailure("Trying to open app with illegal url \(url).")
|
||||
return
|
||||
}
|
||||
|
||||
openRatingsForRecipe(id: recipeID)
|
||||
}
|
||||
|
||||
func open(_ url: URL) {
|
||||
self.openedURL = url
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
private func openRatings(for recipe: Recipe) {
|
||||
tab = recipe.isVegetarian ? .veggie : .meat
|
||||
let recipeListCoordinator = firstReceiver(as: RecipeListCoordinator.self, where: { $0.filter(recipe) })
|
||||
recipeListCoordinator!.open(recipe)
|
||||
let recipeViewModel = firstReceiver(as: RecipeViewModel.self, where: { $0.recipe.id == recipe.id })
|
||||
recipeViewModel!.openRatings()
|
||||
}
|
||||
|
||||
private func openRatingsForRecipe(id: String) {
|
||||
recipeService.fetchRecipe(id: id) { [weak self] recipe in
|
||||
guard let recipe = recipe, let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.openRatings(for: recipe)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
enum HomeTab {
|
||||
case meat
|
||||
@@ -14,42 +15,22 @@ enum HomeTab {
|
||||
case settings
|
||||
}
|
||||
|
||||
class HomeCoordinator: ObservableObject {
|
||||
protocol HomeCoordinator: ViewModel {
|
||||
var tab: HomeTab { get set }
|
||||
var veggieCoordinator: RecipeListCoordinator! { get }
|
||||
var meatCoordinator: RecipeListCoordinator! { get }
|
||||
var openedURL: URL? { get set }
|
||||
|
||||
// MARK: Stored Properties
|
||||
func startDeepLink(from url: URL)
|
||||
func open(_ url: URL)
|
||||
}
|
||||
|
||||
@Published var tab = HomeTab.meat
|
||||
@Published var veggieCoordinator: RecipeListCoordinator!
|
||||
@Published var meatCoordinator: RecipeListCoordinator!
|
||||
extension HomeCoordinator {
|
||||
|
||||
@Published var openedURL: URL?
|
||||
|
||||
private let recipeService: RecipeService
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(recipeService: RecipeService) {
|
||||
self.recipeService = recipeService
|
||||
|
||||
self.veggieCoordinator = .init(
|
||||
title: "Veggie",
|
||||
recipeService: recipeService,
|
||||
parent: self,
|
||||
filter: { $0.isVegetarian }
|
||||
)
|
||||
|
||||
self.meatCoordinator = .init(
|
||||
title: "Meat",
|
||||
recipeService: recipeService,
|
||||
parent: self,
|
||||
filter: { !$0.isVegetarian }
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func open(_ url: URL) {
|
||||
self.openedURL = url
|
||||
@DeepLinkableBuilder
|
||||
var children: [DeepLinkable] {
|
||||
veggieCoordinator
|
||||
meatCoordinator
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
struct HomeCoordinatorView: View {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@ObservedObject var coordinator: HomeCoordinator
|
||||
@Store var coordinator: HomeCoordinator
|
||||
|
||||
// MARK: Views
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// DefaultRatingViewModel.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 07.01.21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class DefaultRatingViewModel: RatingViewModel, ObservableObject, Identifiable {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published private(set) var recipe: Recipe
|
||||
@Published private(set) var meanRating = 0.0
|
||||
@Published private(set) var ratings = [Recipe.Rating]()
|
||||
|
||||
private let recipeService: RecipeService
|
||||
private unowned let coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(recipe: Recipe, recipeService: RecipeService,
|
||||
coordinator: RecipeListCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
self.recipe = recipe
|
||||
self.recipeService = recipeService
|
||||
|
||||
recipeService.fetchRatings(for: recipe) { ratings in
|
||||
self.ratings = ratings
|
||||
self.meanRating = Double(ratings.map(\.value).reduce(0, +)) / Double(ratings.count)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func close() {
|
||||
self.coordinator.closeRatings()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
struct RatingView: View {
|
||||
|
||||
@@ -38,7 +39,7 @@ struct RatingView: View {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@ObservedObject var viewModel: RatingViewModel
|
||||
@Store var viewModel: RatingViewModel
|
||||
|
||||
// MARK: Views
|
||||
|
||||
|
||||
@@ -6,36 +6,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XUI
|
||||
|
||||
class RatingViewModel: ObservableObject, Identifiable {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published var recipe: Recipe
|
||||
@Published var meanRating = 0.0
|
||||
@Published var ratings = [Recipe.Rating]()
|
||||
|
||||
private let recipeService: RecipeService
|
||||
private unowned let coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(recipe: Recipe, recipeService: RecipeService,
|
||||
coordinator: RecipeListCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
self.recipe = recipe
|
||||
self.recipeService = recipeService
|
||||
|
||||
recipeService.fetchRatings(for: recipe) { ratings in
|
||||
self.ratings = ratings
|
||||
self.meanRating = Double(ratings.map(\.value).reduce(0, +)) / Double(ratings.count)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func close() {
|
||||
self.coordinator.closeRatings()
|
||||
}
|
||||
protocol RatingViewModel: ViewModel {
|
||||
var recipe: Recipe { get }
|
||||
var meanRating: Double { get }
|
||||
var ratings: [Recipe.Rating] { get }
|
||||
|
||||
func close()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// DefaultRecipeViewModel.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 07.01.21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class DefaultRecipeViewModel: RecipeViewModel, ObservableObject, Identifiable {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published private(set) var recipe: Recipe
|
||||
|
||||
private unowned let coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(recipe: Recipe, coordinator: RecipeListCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
self.recipe = recipe
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func openRatings() {
|
||||
self.coordinator.openRatings(for: recipe)
|
||||
}
|
||||
|
||||
func open(_ url: URL) {
|
||||
self.coordinator.open(url)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,12 +6,13 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
struct RecipeView<RatingModifier: ViewModifier>: View {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@ObservedObject var viewModel: RecipeViewModel
|
||||
@Store var viewModel: RecipeViewModel
|
||||
let ratingModifier: RatingModifier
|
||||
|
||||
// MARK: Views
|
||||
|
||||
@@ -6,30 +6,11 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XUI
|
||||
|
||||
class RecipeViewModel: ObservableObject, Identifiable {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published var recipe: Recipe
|
||||
|
||||
private unowned let coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(recipe: Recipe, coordinator: RecipeListCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
self.recipe = recipe
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func openRatings() {
|
||||
self.coordinator.openRatings(for: recipe)
|
||||
}
|
||||
|
||||
func open(_ url: URL) {
|
||||
self.coordinator.open(url)
|
||||
}
|
||||
protocol RecipeViewModel: ViewModel {
|
||||
var recipe: Recipe { get }
|
||||
|
||||
func openRatings()
|
||||
func open(_ url: URL)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// DefaultRecipeListViewModel.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 07.01.21.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
class DefaultRecipeListViewModel: ObservableObject, RecipeListViewModel {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published private(set) var title: String
|
||||
@Published private(set) var recipes = [Recipe]()
|
||||
|
||||
private let recipeService: RecipeService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private unowned let coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(title: String,
|
||||
recipeService: RecipeService,
|
||||
coordinator: RecipeListCoordinator,
|
||||
filter: @escaping (Recipe) -> Bool) {
|
||||
|
||||
self.title = title
|
||||
self.coordinator = coordinator
|
||||
self.recipeService = recipeService
|
||||
|
||||
coordinator
|
||||
.objectWillChange
|
||||
.sink(receiveValue: objectWillChange.send)
|
||||
.store(in: &cancellables)
|
||||
|
||||
recipeService.fetchRecipes {
|
||||
self.recipes = $0.filter(filter)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func open(_ recipe: Recipe) {
|
||||
coordinator.open(recipe)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,12 +6,13 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
struct RecipeList: View {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@ObservedObject var viewModel: RecipeListViewModel
|
||||
@Store var viewModel: RecipeListViewModel
|
||||
|
||||
// MARK: Views
|
||||
|
||||
|
||||
@@ -6,42 +6,11 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
extension Identifiable where ID: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
||||
class RecipeListViewModel: ObservableObject {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published var title: String
|
||||
@Published var recipes = [Recipe]()
|
||||
|
||||
private let recipeService: RecipeService
|
||||
private unowned let coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(title: String,
|
||||
recipeService: RecipeService,
|
||||
coordinator: RecipeListCoordinator,
|
||||
filter: @escaping (Recipe) -> Bool) {
|
||||
self.title = title
|
||||
self.coordinator = coordinator
|
||||
self.recipeService = recipeService
|
||||
|
||||
recipeService.fetchRecipes {
|
||||
self.recipes = $0.filter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func open(_ recipe: Recipe) {
|
||||
self.coordinator.open(recipe)
|
||||
}
|
||||
|
||||
protocol RecipeListViewModel: ViewModel {
|
||||
var title: String { get }
|
||||
var recipes: [Recipe] { get }
|
||||
|
||||
func open(_ recipe: Recipe)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// DefaultRecipeListCoordinator.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 07.01.21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XUI
|
||||
|
||||
class DefaultRecipeListCoordinator: ObservableObject, RecipeListCoordinator, Identifiable {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@Published private(set) var viewModel: RecipeListViewModel!
|
||||
@Published var detailViewModel: RecipeViewModel?
|
||||
@Published var ratingViewModel: RatingViewModel?
|
||||
|
||||
private let _filter: (Recipe) -> Bool
|
||||
private let recipeService: RecipeService
|
||||
private unowned let parent: HomeCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(title: String,
|
||||
recipeService: RecipeService,
|
||||
parent: HomeCoordinator,
|
||||
filter: @escaping (Recipe) -> Bool) {
|
||||
self.parent = parent
|
||||
self.recipeService = recipeService
|
||||
self._filter = filter
|
||||
|
||||
self.viewModel = DefaultRecipeListViewModel(
|
||||
title: title,
|
||||
recipeService: recipeService,
|
||||
coordinator: self,
|
||||
filter: filter
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func filter(_ recipe: Recipe) -> Bool {
|
||||
_filter(recipe)
|
||||
}
|
||||
|
||||
func open(_ recipe: Recipe) {
|
||||
detailViewModel = DefaultRecipeViewModel(recipe: recipe, coordinator: self)
|
||||
}
|
||||
|
||||
func openRatings(for recipe: Recipe) {
|
||||
ratingViewModel = DefaultRatingViewModel(recipe: recipe, recipeService: recipeService, coordinator: self)
|
||||
}
|
||||
|
||||
func closeRatings() {
|
||||
ratingViewModel = nil
|
||||
}
|
||||
|
||||
func open(_ url: URL) {
|
||||
parent.open(url)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,51 +6,27 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
class RecipeListCoordinator: ObservableObject, Identifiable {
|
||||
protocol RecipeListCoordinator: ViewModel {
|
||||
var viewModel: RecipeListViewModel! { get }
|
||||
var detailViewModel: RecipeViewModel? { get set }
|
||||
var ratingViewModel: RatingViewModel? { get set }
|
||||
|
||||
// MARK: Stored Properties
|
||||
func filter(_ recipe: Recipe) -> Bool
|
||||
func open(_ recipe: Recipe)
|
||||
func openRatings(for recipe: Recipe)
|
||||
func closeRatings()
|
||||
func open(_ url: URL)
|
||||
}
|
||||
|
||||
@Published var viewModel: RecipeListViewModel!
|
||||
@Published var detailViewModel: RecipeViewModel?
|
||||
@Published var ratingViewModel: RatingViewModel?
|
||||
extension RecipeListCoordinator {
|
||||
|
||||
private let recipeService: RecipeService
|
||||
private unowned let parent: HomeCoordinator
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(title: String,
|
||||
recipeService: RecipeService,
|
||||
parent: HomeCoordinator,
|
||||
filter: @escaping (Recipe) -> Bool) {
|
||||
self.parent = parent
|
||||
self.recipeService = recipeService
|
||||
|
||||
self.viewModel = .init(
|
||||
title: title,
|
||||
recipeService: recipeService,
|
||||
coordinator: self,
|
||||
filter: filter
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func open(_ recipe: Recipe) {
|
||||
self.detailViewModel = .init(recipe: recipe, coordinator: self)
|
||||
}
|
||||
|
||||
func openRatings(for recipe: Recipe) {
|
||||
self.ratingViewModel = .init(recipe: recipe, recipeService: recipeService, coordinator: self)
|
||||
}
|
||||
|
||||
func closeRatings() {
|
||||
self.ratingViewModel = nil
|
||||
}
|
||||
|
||||
func open(_ url: URL) {
|
||||
self.parent.open(url)
|
||||
@DeepLinkableBuilder
|
||||
var children: [DeepLinkable] {
|
||||
viewModel
|
||||
detailViewModel
|
||||
ratingViewModel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,19 +6,20 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
struct RecipeListCoordinatorView: View {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@ObservedObject var coordinator: RecipeListCoordinator
|
||||
@Store var coordinator: RecipeListCoordinator
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
RecipeList(viewModel: coordinator.viewModel)
|
||||
.navigation(item: $coordinator.detailViewModel) { viewModel in
|
||||
.navigation(model: $coordinator.detailViewModel) { viewModel in
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
phoneRecipeView(viewModel)
|
||||
} else {
|
||||
@@ -32,7 +33,7 @@ struct RecipeListCoordinatorView: View {
|
||||
private func phoneRecipeView(_ viewModel: RecipeViewModel) -> some View {
|
||||
RecipeView(
|
||||
viewModel: viewModel,
|
||||
ratingModifier: SheetModifier(item: $coordinator.ratingViewModel) { viewModel in
|
||||
ratingModifier: SheetModifier(model: $coordinator.ratingViewModel) { viewModel in
|
||||
NavigationView {
|
||||
RatingView(viewModel: viewModel)
|
||||
}
|
||||
@@ -44,7 +45,7 @@ struct RecipeListCoordinatorView: View {
|
||||
private func padRecipeView(_ viewModel: RecipeViewModel) -> some View {
|
||||
RecipeView(
|
||||
viewModel: viewModel,
|
||||
ratingModifier: PopoverModifier(item: $coordinator.ratingViewModel) {
|
||||
ratingModifier: PopoverModifier(model: $coordinator.ratingViewModel) {
|
||||
RatingView(viewModel: $0)
|
||||
.frame(width: 500, height: 500)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// Recipes
|
||||
// Recipes (iOS)
|
||||
//
|
||||
// Created by Paul Kraft on 01.01.21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XUI
|
||||
|
||||
struct SettingsView: View {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
@ObservedObject var coordinator: HomeCoordinator
|
||||
@Store var coordinator: HomeCoordinator
|
||||
|
||||
// MARK: Views
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// PopoverModifier.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 05.01.21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PopoverModifier<Item: Identifiable, Destination: View>: ViewModifier {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
private let item: Binding<Item?>
|
||||
private let destination: (Item) -> Destination
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(item: Binding<Item?>,
|
||||
@ViewBuilder content: @escaping (Item) -> Destination) {
|
||||
|
||||
self.item = item
|
||||
self.destination = content
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.popover(item: item, content: destination)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// SheetModifier.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 05.01.21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SheetModifier<Item: Identifiable, Destination: View>: ViewModifier {
|
||||
|
||||
// MARK: Stored Properties
|
||||
|
||||
private let item: Binding<Item?>
|
||||
private let destination: (Item) -> Destination
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(item: Binding<Item?>,
|
||||
@ViewBuilder content: @escaping (Item) -> Destination) {
|
||||
|
||||
self.item = item
|
||||
self.destination = content
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.sheet(item: item, content: destination)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
//
|
||||
// View+Navigation.swift
|
||||
// Recipes
|
||||
//
|
||||
// Created by Paul Kraft on 05.01.21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
|
||||
func onNavigation(_ action: @escaping () -> Void) -> some View {
|
||||
let isActive = Binding(
|
||||
get: { false },
|
||||
set: { newValue in
|
||||
if newValue {
|
||||
action()
|
||||
}
|
||||
}
|
||||
)
|
||||
return NavigationLink(
|
||||
destination: EmptyView(),
|
||||
isActive: isActive
|
||||
) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
func navigation<Item, Destination: View>(
|
||||
item: Binding<Item?>,
|
||||
@ViewBuilder destination: (Item) -> Destination
|
||||
) -> some View {
|
||||
let isActive = Binding(
|
||||
get: { item.wrappedValue != nil },
|
||||
set: { value in
|
||||
if !value {
|
||||
item.wrappedValue = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
return navigation(isActive: isActive) {
|
||||
item.wrappedValue.map(destination)
|
||||
}
|
||||
}
|
||||
|
||||
func navigation<Destination: View>(
|
||||
isActive: Binding<Bool>,
|
||||
@ViewBuilder destination: () -> Destination
|
||||
) -> some View {
|
||||
overlay(
|
||||
NavigationLink(
|
||||
destination: isActive.wrappedValue ? destination() : nil,
|
||||
isActive: isActive,
|
||||
label: { EmptyView() }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension NavigationLink {
|
||||
|
||||
init<T: Identifiable, D: View>(item: Binding<T?>,
|
||||
@ViewBuilder destination: (T) -> D,
|
||||
@ViewBuilder label: () -> Label) where Destination == D? {
|
||||
let isActive = Binding(
|
||||
get: { item.wrappedValue != nil },
|
||||
set: { value in
|
||||
if !value {
|
||||
item.wrappedValue = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
self.init(
|
||||
destination: item.wrappedValue.map(destination),
|
||||
isActive: isActive,
|
||||
label: label
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
//
|
||||
// AsyncImage.swift
|
||||
// Recipes
|
||||
// QuickGit
|
||||
//
|
||||
// Created by Paul Kraft on 04.03.20.
|
||||
// Copyright © 2020 QuickBird Studios. All rights reserved.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RatingStars.swift
|
||||
// Recipes
|
||||
// Recipes (iOS)
|
||||
//
|
||||
// Created by Paul Kraft on 02.01.21.
|
||||
//
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// SafariView.swift
|
||||
// Recipes
|
||||
// Recipes (iOS)
|
||||
//
|
||||
// Created by Paul Kraft on 04.01.21.
|
||||
//
|
||||
@@ -27,3 +27,14 @@ struct SafariView: UIViewControllerRepresentable {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func safariSheet(with binding: Binding<URL?>) -> some View {
|
||||
sheet(item: binding) { url in
|
||||
SafariView(url: url)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user