2 Commits

Author SHA1 Message Date
Paul Kraft 430ae744cc Update XUI 2021-03-01 19:53:58 +01:00
Paul Kraft 98d565ba0d XUI adaptions 2021-03-01 18:00:55 +01:00
29 changed files with 572 additions and 371 deletions
+90
View File
@@ -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/
+79 -28
View File
@@ -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
}
@@ -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>
+6
View File
@@ -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)
+33 -1
View File
@@ -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
)
}
}
+2 -1
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
//
// RatingStars.swift
// Recipes
// Recipes (iOS)
//
// Created by Paul Kraft on 02.01.21.
//
+12 -1
View File
@@ -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)
}
}
}