├── .gitignore ├── LICENSE ├── README.md ├── complete-project ├── TrySyncUps.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── TrySyncUps │ ├── App.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Themes │ │ │ ├── Contents.json │ │ │ ├── appIndigo.colorset │ │ │ └── Contents.json │ │ │ ├── appMagenta.colorset │ │ │ └── Contents.json │ │ │ ├── appOrange.colorset │ │ │ └── Contents.json │ │ │ ├── appPurple.colorset │ │ │ └── Contents.json │ │ │ ├── appTeal.colorset │ │ │ └── Contents.json │ │ │ ├── appYellow.colorset │ │ │ └── Contents.json │ │ │ ├── bubblegum.colorset │ │ │ └── Contents.json │ │ │ ├── buttercup.colorset │ │ │ └── Contents.json │ │ │ ├── lavender.colorset │ │ │ └── Contents.json │ │ │ ├── navy.colorset │ │ │ └── Contents.json │ │ │ ├── oxblood.colorset │ │ │ └── Contents.json │ │ │ ├── periwinkle.colorset │ │ │ └── Contents.json │ │ │ ├── poppy.colorset │ │ │ └── Contents.json │ │ │ ├── seafoam.colorset │ │ │ └── Contents.json │ │ │ ├── sky.colorset │ │ │ └── Contents.json │ │ │ └── tan.colorset │ │ │ └── Contents.json │ ├── Meeting.swift │ ├── Models.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── RecordMeeting.swift │ ├── SyncUpDetail.swift │ ├── SyncUpForm.swift │ └── SyncUpsList.swift └── TrySyncUpsTests │ ├── RecordMeetingsTests.swift │ ├── SyncUpDetailTests.swift │ ├── SyncUpFormTests.swift │ └── SyncUpsListTests.swift ├── in-progress ├── TrySyncUps.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── TrySyncUps.xcscheme ├── TrySyncUps │ ├── App.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Themes │ │ │ ├── Contents.json │ │ │ ├── appIndigo.colorset │ │ │ └── Contents.json │ │ │ ├── appMagenta.colorset │ │ │ └── Contents.json │ │ │ ├── appOrange.colorset │ │ │ └── Contents.json │ │ │ ├── appPurple.colorset │ │ │ └── Contents.json │ │ │ ├── appTeal.colorset │ │ │ └── Contents.json │ │ │ ├── bubblegum.colorset │ │ │ └── Contents.json │ │ │ ├── buttercup.colorset │ │ │ └── Contents.json │ │ │ ├── lavender.colorset │ │ │ └── Contents.json │ │ │ ├── navy.colorset │ │ │ └── Contents.json │ │ │ ├── oxblood.colorset │ │ │ └── Contents.json │ │ │ ├── periwinkle.colorset │ │ │ └── Contents.json │ │ │ ├── poppy.colorset │ │ │ └── Contents.json │ │ │ ├── seafoam.colorset │ │ │ └── Contents.json │ │ │ ├── sky.colorset │ │ │ └── Contents.json │ │ │ ├── tan.colorset │ │ │ └── Contents.json │ │ │ └── yellow.colorset │ │ │ └── Contents.json │ ├── Meeting.swift │ ├── Models.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── RecordMeeting.swift │ ├── SyncUpDetail.swift │ ├── SyncUpForm.swift │ └── SyncUpsList.swift └── TrySyncUpsTests │ ├── RecordMeetingsTests.swift │ ├── SyncUpDetailTests.swift │ ├── SyncUpFormTests.swift │ ├── SyncUpsListTests.swift │ └── TrySyncUps.xctestplan └── starting-point ├── TrySyncUps.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── TrySyncUps.xcscheme ├── TrySyncUps ├── App.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Themes │ │ ├── Contents.json │ │ ├── appIndigo.colorset │ │ └── Contents.json │ │ ├── appMagenta.colorset │ │ └── Contents.json │ │ ├── appOrange.colorset │ │ └── Contents.json │ │ ├── appPurple.colorset │ │ └── Contents.json │ │ ├── appTeal.colorset │ │ └── Contents.json │ │ ├── bubblegum.colorset │ │ └── Contents.json │ │ ├── buttercup.colorset │ │ └── Contents.json │ │ ├── lavender.colorset │ │ └── Contents.json │ │ ├── navy.colorset │ │ └── Contents.json │ │ ├── oxblood.colorset │ │ └── Contents.json │ │ ├── periwinkle.colorset │ │ └── Contents.json │ │ ├── poppy.colorset │ │ └── Contents.json │ │ ├── seafoam.colorset │ │ └── Contents.json │ │ ├── sky.colorset │ │ └── Contents.json │ │ ├── tan.colorset │ │ └── Contents.json │ │ └── yellow.colorset │ │ └── Contents.json ├── Meeting.swift ├── Models.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── RecordMeeting.swift ├── SyncUpDetail.swift ├── SyncUpForm.swift └── SyncUpsList.swift └── TrySyncUpsTests ├── RecordMeetingsTests.swift ├── SyncUpDetailTests.swift ├── SyncUpFormTests.swift ├── SyncUpsListTests.swift └── TrySyncUps.xctestplan /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | .DS_Store 93 | */.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Point-Free 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrySyncUps 2 | 3 | The starting project for our try! Swift 2024 Composable Architecture workshop. 4 | 5 | Resources: 6 | 7 | - [The Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture) 8 | - [Point-Free Slack Community](https://www.pointfree.co/slack-invite) 9 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DC531C482BA3CF1E005C7270 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C472BA3CF1E005C7270 /* App.swift */; }; 11 | DC531C4C2BA3CF20005C7270 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC531C4B2BA3CF20005C7270 /* Assets.xcassets */; }; 12 | DC531C4F2BA3CF20005C7270 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC531C4E2BA3CF20005C7270 /* Preview Assets.xcassets */; }; 13 | DC531C592BA3CF20005C7270 /* RecordMeetingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C582BA3CF20005C7270 /* RecordMeetingsTests.swift */; }; 14 | DC531C732BA3CF4F005C7270 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = DC531C722BA3CF4F005C7270 /* ComposableArchitecture */; }; 15 | DC531C752BA3CFA1005C7270 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C742BA3CFA1005C7270 /* Models.swift */; }; 16 | DC531C772BA3D014005C7270 /* SyncUpsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C762BA3D014005C7270 /* SyncUpsList.swift */; }; 17 | DC531C792BA3D420005C7270 /* SyncUpForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C782BA3D420005C7270 /* SyncUpForm.swift */; }; 18 | DC531C7B2BA3D649005C7270 /* SyncUpDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C7A2BA3D649005C7270 /* SyncUpDetail.swift */; }; 19 | DC531C7D2BA3D84A005C7270 /* Meeting.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C7C2BA3D84A005C7270 /* Meeting.swift */; }; 20 | DC531C7F2BA3D8C1005C7270 /* RecordMeeting.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C7E2BA3D8C1005C7270 /* RecordMeeting.swift */; }; 21 | DC531C812BA426C1005C7270 /* SyncUpDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C802BA426C1005C7270 /* SyncUpDetailTests.swift */; }; 22 | DC531C832BA426CC005C7270 /* SyncUpFormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C822BA426CC005C7270 /* SyncUpFormTests.swift */; }; 23 | DC531C852BA426DB005C7270 /* SyncUpsListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C842BA426DB005C7270 /* SyncUpsListTests.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | DC531C552BA3CF20005C7270 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = DC531C3C2BA3CF1E005C7270 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = DC531C432BA3CF1E005C7270; 32 | remoteInfo = TrySyncUps; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | DC531C442BA3CF1E005C7270 /* TrySyncUps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TrySyncUps.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | DC531C472BA3CF1E005C7270 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 39 | DC531C4B2BA3CF20005C7270 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | DC531C4E2BA3CF20005C7270 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 41 | DC531C542BA3CF20005C7270 /* TrySyncUpsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TrySyncUpsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | DC531C582BA3CF20005C7270 /* RecordMeetingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordMeetingsTests.swift; sourceTree = ""; }; 43 | DC531C742BA3CFA1005C7270 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 44 | DC531C762BA3D014005C7270 /* SyncUpsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpsList.swift; sourceTree = ""; }; 45 | DC531C782BA3D420005C7270 /* SyncUpForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpForm.swift; sourceTree = ""; }; 46 | DC531C7A2BA3D649005C7270 /* SyncUpDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpDetail.swift; sourceTree = ""; }; 47 | DC531C7C2BA3D84A005C7270 /* Meeting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Meeting.swift; sourceTree = ""; }; 48 | DC531C7E2BA3D8C1005C7270 /* RecordMeeting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordMeeting.swift; sourceTree = ""; }; 49 | DC531C802BA426C1005C7270 /* SyncUpDetailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpDetailTests.swift; sourceTree = ""; }; 50 | DC531C822BA426CC005C7270 /* SyncUpFormTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpFormTests.swift; sourceTree = ""; }; 51 | DC531C842BA426DB005C7270 /* SyncUpsListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpsListTests.swift; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | DC531C412BA3CF1E005C7270 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | DC531C732BA3CF4F005C7270 /* ComposableArchitecture in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | DC531C512BA3CF20005C7270 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | DC531C3B2BA3CF1E005C7270 = { 74 | isa = PBXGroup; 75 | children = ( 76 | DC531C462BA3CF1E005C7270 /* TrySyncUps */, 77 | DC531C572BA3CF20005C7270 /* TrySyncUpsTests */, 78 | DC531C452BA3CF1E005C7270 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | DC531C452BA3CF1E005C7270 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | DC531C442BA3CF1E005C7270 /* TrySyncUps.app */, 86 | DC531C542BA3CF20005C7270 /* TrySyncUpsTests.xctest */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | DC531C462BA3CF1E005C7270 /* TrySyncUps */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | DC531C472BA3CF1E005C7270 /* App.swift */, 95 | DC531C7C2BA3D84A005C7270 /* Meeting.swift */, 96 | DC531C742BA3CFA1005C7270 /* Models.swift */, 97 | DC531C7E2BA3D8C1005C7270 /* RecordMeeting.swift */, 98 | DC531C7A2BA3D649005C7270 /* SyncUpDetail.swift */, 99 | DC531C782BA3D420005C7270 /* SyncUpForm.swift */, 100 | DC531C762BA3D014005C7270 /* SyncUpsList.swift */, 101 | DC531C4B2BA3CF20005C7270 /* Assets.xcassets */, 102 | DC531C4D2BA3CF20005C7270 /* Preview Content */, 103 | ); 104 | path = TrySyncUps; 105 | sourceTree = ""; 106 | }; 107 | DC531C4D2BA3CF20005C7270 /* Preview Content */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | DC531C4E2BA3CF20005C7270 /* Preview Assets.xcassets */, 111 | ); 112 | path = "Preview Content"; 113 | sourceTree = ""; 114 | }; 115 | DC531C572BA3CF20005C7270 /* TrySyncUpsTests */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | DC531C582BA3CF20005C7270 /* RecordMeetingsTests.swift */, 119 | DC531C802BA426C1005C7270 /* SyncUpDetailTests.swift */, 120 | DC531C822BA426CC005C7270 /* SyncUpFormTests.swift */, 121 | DC531C842BA426DB005C7270 /* SyncUpsListTests.swift */, 122 | ); 123 | path = TrySyncUpsTests; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | DC531C432BA3CF1E005C7270 /* TrySyncUps */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = DC531C682BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUps" */; 132 | buildPhases = ( 133 | DC531C402BA3CF1E005C7270 /* Sources */, 134 | DC531C412BA3CF1E005C7270 /* Frameworks */, 135 | DC531C422BA3CF1E005C7270 /* Resources */, 136 | ); 137 | buildRules = ( 138 | ); 139 | dependencies = ( 140 | ); 141 | name = TrySyncUps; 142 | packageProductDependencies = ( 143 | DC531C722BA3CF4F005C7270 /* ComposableArchitecture */, 144 | ); 145 | productName = TrySyncUps; 146 | productReference = DC531C442BA3CF1E005C7270 /* TrySyncUps.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | DC531C532BA3CF20005C7270 /* TrySyncUpsTests */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = DC531C6B2BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUpsTests" */; 152 | buildPhases = ( 153 | DC531C502BA3CF20005C7270 /* Sources */, 154 | DC531C512BA3CF20005C7270 /* Frameworks */, 155 | DC531C522BA3CF20005C7270 /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | DC531C562BA3CF20005C7270 /* PBXTargetDependency */, 161 | ); 162 | name = TrySyncUpsTests; 163 | productName = TrySyncUpsTests; 164 | productReference = DC531C542BA3CF20005C7270 /* TrySyncUpsTests.xctest */; 165 | productType = "com.apple.product-type.bundle.unit-test"; 166 | }; 167 | /* End PBXNativeTarget section */ 168 | 169 | /* Begin PBXProject section */ 170 | DC531C3C2BA3CF1E005C7270 /* Project object */ = { 171 | isa = PBXProject; 172 | attributes = { 173 | BuildIndependentTargetsInParallel = 1; 174 | LastSwiftUpdateCheck = 1530; 175 | LastUpgradeCheck = 1530; 176 | TargetAttributes = { 177 | DC531C432BA3CF1E005C7270 = { 178 | CreatedOnToolsVersion = 15.3; 179 | }; 180 | DC531C532BA3CF20005C7270 = { 181 | CreatedOnToolsVersion = 15.3; 182 | TestTargetID = DC531C432BA3CF1E005C7270; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = DC531C3F2BA3CF1E005C7270 /* Build configuration list for PBXProject "TrySyncUps" */; 187 | compatibilityVersion = "Xcode 14.0"; 188 | developmentRegion = en; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | en, 192 | Base, 193 | ); 194 | mainGroup = DC531C3B2BA3CF1E005C7270; 195 | packageReferences = ( 196 | DC531C712BA3CF4F005C7270 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, 197 | ); 198 | productRefGroup = DC531C452BA3CF1E005C7270 /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | DC531C432BA3CF1E005C7270 /* TrySyncUps */, 203 | DC531C532BA3CF20005C7270 /* TrySyncUpsTests */, 204 | ); 205 | }; 206 | /* End PBXProject section */ 207 | 208 | /* Begin PBXResourcesBuildPhase section */ 209 | DC531C422BA3CF1E005C7270 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | DC531C4F2BA3CF20005C7270 /* Preview Assets.xcassets in Resources */, 214 | DC531C4C2BA3CF20005C7270 /* Assets.xcassets in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | DC531C522BA3CF20005C7270 /* Resources */ = { 219 | isa = PBXResourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXResourcesBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | DC531C402BA3CF1E005C7270 /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | DC531C752BA3CFA1005C7270 /* Models.swift in Sources */, 233 | DC531C7D2BA3D84A005C7270 /* Meeting.swift in Sources */, 234 | DC531C7B2BA3D649005C7270 /* SyncUpDetail.swift in Sources */, 235 | DC531C792BA3D420005C7270 /* SyncUpForm.swift in Sources */, 236 | DC531C7F2BA3D8C1005C7270 /* RecordMeeting.swift in Sources */, 237 | DC531C772BA3D014005C7270 /* SyncUpsList.swift in Sources */, 238 | DC531C482BA3CF1E005C7270 /* App.swift in Sources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | DC531C502BA3CF20005C7270 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | DC531C812BA426C1005C7270 /* SyncUpDetailTests.swift in Sources */, 247 | DC531C852BA426DB005C7270 /* SyncUpsListTests.swift in Sources */, 248 | DC531C832BA426CC005C7270 /* SyncUpFormTests.swift in Sources */, 249 | DC531C592BA3CF20005C7270 /* RecordMeetingsTests.swift in Sources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXSourcesBuildPhase section */ 254 | 255 | /* Begin PBXTargetDependency section */ 256 | DC531C562BA3CF20005C7270 /* PBXTargetDependency */ = { 257 | isa = PBXTargetDependency; 258 | target = DC531C432BA3CF1E005C7270 /* TrySyncUps */; 259 | targetProxy = DC531C552BA3CF20005C7270 /* PBXContainerItemProxy */; 260 | }; 261 | /* End PBXTargetDependency section */ 262 | 263 | /* Begin XCBuildConfiguration section */ 264 | DC531C662BA3CF20005C7270 /* Debug */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ALWAYS_SEARCH_USER_PATHS = NO; 268 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 269 | CLANG_ANALYZER_NONNULL = YES; 270 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 272 | CLANG_ENABLE_MODULES = YES; 273 | CLANG_ENABLE_OBJC_ARC = YES; 274 | CLANG_ENABLE_OBJC_WEAK = YES; 275 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 276 | CLANG_WARN_BOOL_CONVERSION = YES; 277 | CLANG_WARN_COMMA = YES; 278 | CLANG_WARN_CONSTANT_CONVERSION = YES; 279 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 280 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 281 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INFINITE_RECURSION = YES; 285 | CLANG_WARN_INT_CONVERSION = YES; 286 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = dwarf; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | ENABLE_TESTABILITY = YES; 301 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu17; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_OPTIMIZATION_LEVEL = 0; 306 | GCC_PREPROCESSOR_DEFINITIONS = ( 307 | "DEBUG=1", 308 | "$(inherited)", 309 | ); 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 17.4; 317 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 318 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 319 | MTL_FAST_MATH = YES; 320 | ONLY_ACTIVE_ARCH = YES; 321 | SDKROOT = iphoneos; 322 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 323 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 324 | }; 325 | name = Debug; 326 | }; 327 | DC531C672BA3CF20005C7270 /* Release */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_ENABLE_OBJC_WEAK = YES; 338 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_COMMA = YES; 341 | CLANG_WARN_CONSTANT_CONVERSION = YES; 342 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INFINITE_RECURSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | COPY_PHASE_STRIP = NO; 361 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 362 | ENABLE_NS_ASSERTIONS = NO; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu17; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 17.4; 374 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 375 | MTL_ENABLE_DEBUG_INFO = NO; 376 | MTL_FAST_MATH = YES; 377 | SDKROOT = iphoneos; 378 | SWIFT_COMPILATION_MODE = wholemodule; 379 | VALIDATE_PRODUCT = YES; 380 | }; 381 | name = Release; 382 | }; 383 | DC531C692BA3CF20005C7270 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 388 | CODE_SIGN_STYLE = Automatic; 389 | CURRENT_PROJECT_VERSION = 1; 390 | DEVELOPMENT_ASSET_PATHS = "\"TrySyncUps/Preview Content\""; 391 | ENABLE_PREVIEWS = YES; 392 | GENERATE_INFOPLIST_FILE = YES; 393 | INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "To transcribe meeting notes."; 394 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 395 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 396 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 397 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 398 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 399 | LD_RUNPATH_SEARCH_PATHS = ( 400 | "$(inherited)", 401 | "@executable_path/Frameworks", 402 | ); 403 | MARKETING_VERSION = 1.0; 404 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUps; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | SWIFT_EMIT_LOC_STRINGS = YES; 407 | SWIFT_VERSION = 5.0; 408 | TARGETED_DEVICE_FAMILY = "1,2"; 409 | }; 410 | name = Debug; 411 | }; 412 | DC531C6A2BA3CF20005C7270 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 417 | CODE_SIGN_STYLE = Automatic; 418 | CURRENT_PROJECT_VERSION = 1; 419 | DEVELOPMENT_ASSET_PATHS = "\"TrySyncUps/Preview Content\""; 420 | ENABLE_PREVIEWS = YES; 421 | GENERATE_INFOPLIST_FILE = YES; 422 | INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "To transcribe meeting notes."; 423 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 424 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 425 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 426 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 427 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 428 | LD_RUNPATH_SEARCH_PATHS = ( 429 | "$(inherited)", 430 | "@executable_path/Frameworks", 431 | ); 432 | MARKETING_VERSION = 1.0; 433 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUps; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | SWIFT_EMIT_LOC_STRINGS = YES; 436 | SWIFT_VERSION = 5.0; 437 | TARGETED_DEVICE_FAMILY = "1,2"; 438 | }; 439 | name = Release; 440 | }; 441 | DC531C6C2BA3CF20005C7270 /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 445 | BUNDLE_LOADER = "$(TEST_HOST)"; 446 | CODE_SIGN_STYLE = Automatic; 447 | CURRENT_PROJECT_VERSION = 1; 448 | GENERATE_INFOPLIST_FILE = YES; 449 | IPHONEOS_DEPLOYMENT_TARGET = 17.4; 450 | MARKETING_VERSION = 1.0; 451 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUpsTests; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | SWIFT_EMIT_LOC_STRINGS = NO; 454 | SWIFT_VERSION = 5.0; 455 | TARGETED_DEVICE_FAMILY = "1,2"; 456 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TrySyncUps.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TrySyncUps"; 457 | }; 458 | name = Debug; 459 | }; 460 | DC531C6D2BA3CF20005C7270 /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 464 | BUNDLE_LOADER = "$(TEST_HOST)"; 465 | CODE_SIGN_STYLE = Automatic; 466 | CURRENT_PROJECT_VERSION = 1; 467 | GENERATE_INFOPLIST_FILE = YES; 468 | IPHONEOS_DEPLOYMENT_TARGET = 17.4; 469 | MARKETING_VERSION = 1.0; 470 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUpsTests; 471 | PRODUCT_NAME = "$(TARGET_NAME)"; 472 | SWIFT_EMIT_LOC_STRINGS = NO; 473 | SWIFT_VERSION = 5.0; 474 | TARGETED_DEVICE_FAMILY = "1,2"; 475 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TrySyncUps.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TrySyncUps"; 476 | }; 477 | name = Release; 478 | }; 479 | /* End XCBuildConfiguration section */ 480 | 481 | /* Begin XCConfigurationList section */ 482 | DC531C3F2BA3CF1E005C7270 /* Build configuration list for PBXProject "TrySyncUps" */ = { 483 | isa = XCConfigurationList; 484 | buildConfigurations = ( 485 | DC531C662BA3CF20005C7270 /* Debug */, 486 | DC531C672BA3CF20005C7270 /* Release */, 487 | ); 488 | defaultConfigurationIsVisible = 0; 489 | defaultConfigurationName = Release; 490 | }; 491 | DC531C682BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUps" */ = { 492 | isa = XCConfigurationList; 493 | buildConfigurations = ( 494 | DC531C692BA3CF20005C7270 /* Debug */, 495 | DC531C6A2BA3CF20005C7270 /* Release */, 496 | ); 497 | defaultConfigurationIsVisible = 0; 498 | defaultConfigurationName = Release; 499 | }; 500 | DC531C6B2BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUpsTests" */ = { 501 | isa = XCConfigurationList; 502 | buildConfigurations = ( 503 | DC531C6C2BA3CF20005C7270 /* Debug */, 504 | DC531C6D2BA3CF20005C7270 /* Release */, 505 | ); 506 | defaultConfigurationIsVisible = 0; 507 | defaultConfigurationName = Release; 508 | }; 509 | /* End XCConfigurationList section */ 510 | 511 | /* Begin XCRemoteSwiftPackageReference section */ 512 | DC531C712BA3CF4F005C7270 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { 513 | isa = XCRemoteSwiftPackageReference; 514 | repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture.git"; 515 | requirement = { 516 | branch = "shared-state-beta"; 517 | kind = branch; 518 | }; 519 | }; 520 | /* End XCRemoteSwiftPackageReference section */ 521 | 522 | /* Begin XCSwiftPackageProductDependency section */ 523 | DC531C722BA3CF4F005C7270 /* ComposableArchitecture */ = { 524 | isa = XCSwiftPackageProductDependency; 525 | package = DC531C712BA3CF4F005C7270 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; 526 | productName = ComposableArchitecture; 527 | }; 528 | /* End XCSwiftPackageProductDependency section */ 529 | }; 530 | rootObject = DC531C3C2BA3CF1E005C7270 /* Project object */; 531 | } 532 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "combine-schedulers", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/pointfreeco/combine-schedulers", 7 | "state" : { 8 | "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", 9 | "version" : "1.0.0" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-case-paths", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/pointfreeco/swift-case-paths", 16 | "state" : { 17 | "revision" : "e593aba2c6222daad7c4f2732a431eed2c09bb07", 18 | "version" : "1.3.0" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-clocks", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/pointfreeco/swift-clocks", 25 | "state" : { 26 | "revision" : "a8421d68068d8f45fbceb418fbf22c5dad4afd33", 27 | "version" : "1.0.2" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-collections", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/apple/swift-collections", 34 | "state" : { 35 | "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", 36 | "version" : "1.1.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-composable-architecture", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", 43 | "state" : { 44 | "branch" : "shared-state-beta", 45 | "revision" : "69d6a9e85e7feb8ba4ca9cad83e4c60c2dc1460d" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-concurrency-extras", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/pointfreeco/swift-concurrency-extras", 52 | "state" : { 53 | "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", 54 | "version" : "1.1.0" 55 | } 56 | }, 57 | { 58 | "identity" : "swift-custom-dump", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 61 | "state" : { 62 | "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", 63 | "version" : "1.3.0" 64 | } 65 | }, 66 | { 67 | "identity" : "swift-dependencies", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/pointfreeco/swift-dependencies", 70 | "state" : { 71 | "revision" : "d3a5af3038a09add4d7682f66555d6212058a3c0", 72 | "version" : "1.2.2" 73 | } 74 | }, 75 | { 76 | "identity" : "swift-identified-collections", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/pointfreeco/swift-identified-collections", 79 | "state" : { 80 | "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", 81 | "version" : "1.0.0" 82 | } 83 | }, 84 | { 85 | "identity" : "swift-perception", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/pointfreeco/swift-perception", 88 | "state" : { 89 | "revision" : "a5bb578d963fcdbffe4fd56c92b2e222f5b02c8a", 90 | "version" : "1.1.2" 91 | } 92 | }, 93 | { 94 | "identity" : "swift-syntax", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/apple/swift-syntax", 97 | "state" : { 98 | "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", 99 | "version" : "510.0.1" 100 | } 101 | }, 102 | { 103 | "identity" : "swiftui-navigation", 104 | "kind" : "remoteSourceControl", 105 | "location" : "https://github.com/pointfreeco/swiftui-navigation", 106 | "state" : { 107 | "revision" : "d9e72f3083c08375794afa216fb2f89c0114f303", 108 | "version" : "1.2.1" 109 | } 110 | }, 111 | { 112 | "identity" : "xctest-dynamic-overlay", 113 | "kind" : "remoteSourceControl", 114 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 115 | "state" : { 116 | "revision" : "b13b1d1a8e787a5ffc71ac19dcaf52183ab27ba2", 117 | "version" : "1.1.1" 118 | } 119 | } 120 | ], 121 | "version" : 2 122 | } 123 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/App.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @main 5 | struct SyncUpsApp: App { 6 | var body: some Scene { 7 | WindowGroup { 8 | NavigationStack { 9 | SyncUpsListView( 10 | store: Store( 11 | initialState: SyncUpsListFeature.State( 12 | // syncUpDetail: SyncUpDetailFeature.State( 13 | // editSyncUp: SyncUpFormFeature.State( 14 | // syncUp: .mock 15 | // ), 16 | // syncUp: Shared(.mock) 17 | // ) 18 | ) 19 | ) { 20 | SyncUpsListFeature()._printChanges() 21 | } 22 | ) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/appIndigo.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.443", 9 | "green" : "0.000", 10 | "red" : "0.212" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.443", 27 | "green" : "0.000", 28 | "red" : "0.212" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/appMagenta.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.467", 9 | "green" : "0.075", 10 | "red" : "0.647" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.467", 27 | "green" : "0.075", 28 | "red" : "0.647" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/appOrange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.259", 9 | "green" : "0.545", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.259", 27 | "green" : "0.545", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/appPurple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.949", 9 | "green" : "0.294", 10 | "red" : "0.569" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.949", 27 | "green" : "0.294", 28 | "red" : "0.569" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/appTeal.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.620", 9 | "green" : "0.561", 10 | "red" : "0.133" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.620", 27 | "green" : "0.561", 28 | "red" : "0.133" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/appYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.302", 9 | "green" : "0.875", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.302", 27 | "green" : "0.875", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/bubblegum.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.820", 9 | "green" : "0.502", 10 | "red" : "0.933" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.820", 27 | "green" : "0.502", 28 | "red" : "0.933" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/buttercup.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.588", 9 | "green" : "0.945", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.588", 27 | "green" : "0.945", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/lavender.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.808", 10 | "red" : "0.812" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.808", 28 | "red" : "0.812" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/navy.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.255", 9 | "green" : "0.078", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.255", 27 | "green" : "0.078", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/oxblood.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.043", 9 | "green" : "0.027", 10 | "red" : "0.290" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.043", 27 | "green" : "0.027", 28 | "red" : "0.290" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/periwinkle.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.510", 10 | "red" : "0.525" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.510", 28 | "red" : "0.525" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/poppy.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.369", 9 | "green" : "0.369", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.369", 27 | "green" : "0.369", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/seafoam.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.898", 9 | "green" : "0.918", 10 | "red" : "0.796" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.898", 27 | "green" : "0.918", 28 | "red" : "0.796" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/sky.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.573", 10 | "red" : "0.431" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.573", 28 | "red" : "0.431" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Assets.xcassets/Themes/tan.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.494", 9 | "green" : "0.608", 10 | "red" : "0.761" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.494", 27 | "green" : "0.608", 28 | "red" : "0.761" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Meeting.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MeetingView: View { 4 | let meeting: Meeting 5 | let syncUp: SyncUp 6 | 7 | var body: some View { 8 | Form { 9 | Section { 10 | ForEach(syncUp.attendees) { attendee in 11 | Text(attendee.name) 12 | } 13 | } header: { 14 | Text("Attendees") 15 | } 16 | Section { 17 | Text(meeting.transcript) 18 | } header: { 19 | Text("Transcript") 20 | } 21 | } 22 | .navigationTitle(Text(meeting.date, style: .date)) 23 | } 24 | } 25 | 26 | #Preview { 27 | NavigationStack { 28 | MeetingView(meeting: SyncUp.mock.meetings[0], syncUp: .mock) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Models.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | struct SyncUp: Equatable, Identifiable, Codable { 5 | let id: UUID 6 | var attendees: IdentifiedArrayOf = [] 7 | var duration: Duration = .seconds(60 * 5) 8 | var meetings: IdentifiedArrayOf = [] 9 | var theme: Theme = .bubblegum 10 | var title = "" 11 | 12 | var durationPerAttendee: Duration { 13 | duration / attendees.count 14 | } 15 | } 16 | 17 | struct Attendee: Equatable, Identifiable, Codable { 18 | let id: UUID 19 | var name = "" 20 | } 21 | 22 | struct Meeting: Equatable, Identifiable, Codable { 23 | let id: UUID 24 | let date: Date 25 | var transcript: String 26 | } 27 | 28 | enum Theme: String, CaseIterable, Equatable, Identifiable, Codable { 29 | case appIndigo 30 | case appMagenta 31 | case appOrange 32 | case appPurple 33 | case appTeal 34 | case yellow 35 | case bubblegum 36 | case buttercup 37 | case lavender 38 | case navy 39 | case oxblood 40 | case periwinkle 41 | case poppy 42 | case seafoam 43 | case sky 44 | case tan 45 | 46 | var id: Self { self } 47 | 48 | var accentColor: Color { 49 | switch self { 50 | case .appOrange, .appTeal, .yellow, .bubblegum, .buttercup, .lavender, .periwinkle, .poppy, 51 | .seafoam, .sky, .tan: 52 | return .black 53 | case .appIndigo, .appMagenta, .appPurple, .navy, .oxblood: 54 | return .white 55 | } 56 | } 57 | 58 | var mainColor: Color { Color(rawValue) } 59 | 60 | var name: String { 61 | switch self { 62 | case .appIndigo: 63 | "indigo" 64 | case .appMagenta: 65 | "magent" 66 | case .appOrange: 67 | "orange" 68 | case .appPurple: 69 | "purple" 70 | case .appTeal: 71 | "teal" 72 | case .yellow: 73 | "yellow" 74 | case .bubblegum: 75 | "bubblegum" 76 | case .buttercup: 77 | "buttercup" 78 | case .lavender: 79 | "lavender" 80 | case .navy: 81 | "navy" 82 | case .oxblood: 83 | "oxblood" 84 | case .periwinkle: 85 | "periwinkle" 86 | case .poppy: 87 | "poppy" 88 | case .seafoam: 89 | "seafoam" 90 | case .sky: 91 | "sky" 92 | case .tan: 93 | "tan" 94 | } 95 | } 96 | } 97 | 98 | extension SyncUp { 99 | static let mock = Self( 100 | id: SyncUp.ID(), 101 | attendees: [ 102 | Attendee(id: Attendee.ID(), name: "Blob"), 103 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 104 | Attendee(id: Attendee.ID(), name: "Blob Sr"), 105 | Attendee(id: Attendee.ID(), name: "Blob Esq"), 106 | Attendee(id: Attendee.ID(), name: "Blob III"), 107 | Attendee(id: Attendee.ID(), name: "Blob I"), 108 | ], 109 | duration: .seconds(60), 110 | meetings: [ 111 | Meeting( 112 | id: Meeting.ID(), 113 | date: Date().addingTimeInterval(-60 * 60 * 24 * 7), 114 | transcript: """ 115 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \ 116 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \ 117 | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure \ 118 | dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \ 119 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \ 120 | mollit anim id est laborum. 121 | """ 122 | ) 123 | ], 124 | theme: .appOrange, 125 | title: "Design" 126 | ) 127 | 128 | static let engineeringMock = Self( 129 | id: SyncUp.ID(), 130 | attendees: [ 131 | Attendee(id: Attendee.ID(), name: "Blob"), 132 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 133 | ], 134 | duration: .seconds(60 * 10), 135 | theme: .periwinkle, 136 | title: "Engineering" 137 | ) 138 | 139 | static let productMock = Self( 140 | id: SyncUp.ID(), 141 | attendees: [ 142 | Attendee(id: Attendee.ID(), name: "Blob Sr"), 143 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 144 | ], 145 | duration: .seconds(60 * 30), 146 | theme: .poppy, 147 | title: "Product" 148 | ) 149 | } 150 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/RecordMeeting.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct RecordMeetingFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | let syncUp: SyncUp 9 | var secondsElapsed = 0 10 | var speakerIndex = 0 11 | 12 | var durationRemaining: Duration { 13 | syncUp.duration - .seconds(secondsElapsed) 14 | } 15 | } 16 | enum Action { 17 | case onAppear 18 | case timerTick 19 | } 20 | var body: some ReducerOf { 21 | Reduce { state, action in 22 | switch action { 23 | case .onAppear: 24 | return .run { send in 25 | while true { 26 | try await Task.sleep(for: .seconds(1)) 27 | await send(.timerTick) 28 | } 29 | } 30 | case .timerTick: 31 | state.secondsElapsed += 1 32 | let secondsPerAttendee = Int(state.syncUp.durationPerAttendee.components.seconds) 33 | if state.secondsElapsed.isMultiple(of: secondsPerAttendee) { 34 | if state.speakerIndex == state.syncUp.attendees.count - 1 { 35 | return .none 36 | } 37 | state.speakerIndex += 1 38 | } 39 | return .none 40 | } 41 | } 42 | } 43 | } 44 | 45 | struct RecordMeetingView: View { 46 | let store: StoreOf 47 | 48 | var body: some View { 49 | ZStack { 50 | RoundedRectangle(cornerRadius: 16) 51 | .fill(store.syncUp.theme.mainColor) 52 | 53 | VStack { 54 | MeetingHeaderView( 55 | secondsElapsed: store.secondsElapsed, 56 | durationRemaining: store.durationRemaining, 57 | theme: store.syncUp.theme 58 | ) 59 | MeetingTimerView( 60 | syncUp: store.syncUp, 61 | speakerIndex: store.speakerIndex 62 | ) 63 | MeetingFooterView( 64 | syncUp: store.syncUp, 65 | nextButtonTapped: { 66 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 67 | }, 68 | speakerIndex: store.speakerIndex 69 | ) 70 | } 71 | } 72 | .padding() 73 | .foregroundColor(store.syncUp.theme.accentColor) 74 | .navigationBarTitleDisplayMode(.inline) 75 | .toolbar { 76 | ToolbarItem(placement: .cancellationAction) { 77 | Button("End meeting") { 78 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 79 | } 80 | } 81 | } 82 | .interactiveDismissDisabled(true) 83 | .navigationBarBackButtonHidden(true) 84 | .onAppear { 85 | store.send(.onAppear) 86 | } 87 | } 88 | } 89 | 90 | #Preview { 91 | NavigationStack { 92 | RecordMeetingView( 93 | store: Store(initialState: RecordMeetingFeature.State(syncUp: .mock)) { 94 | RecordMeetingFeature() 95 | } 96 | ) 97 | } 98 | } 99 | 100 | struct MeetingHeaderView: View { 101 | let secondsElapsed: Int 102 | let durationRemaining: Duration 103 | let theme: Theme 104 | 105 | var body: some View { 106 | VStack { 107 | ProgressView(value: progress) 108 | .progressViewStyle(MeetingProgressViewStyle(theme: theme)) 109 | HStack { 110 | VStack(alignment: .leading) { 111 | Text("Time Elapsed") 112 | .font(.caption) 113 | Label( 114 | Duration.seconds(secondsElapsed).formatted(.units()), 115 | systemImage: "hourglass.bottomhalf.fill" 116 | ) 117 | } 118 | Spacer() 119 | VStack(alignment: .trailing) { 120 | Text("Time Remaining") 121 | .font(.caption) 122 | Label(durationRemaining.formatted(.units()), systemImage: "hourglass.tophalf.fill") 123 | .font(.body.monospacedDigit()) 124 | //.labelStyle(.trailingIcon) 125 | } 126 | } 127 | } 128 | .padding([.top, .horizontal]) 129 | } 130 | 131 | private var totalDuration: Duration { 132 | .seconds(secondsElapsed) + durationRemaining 133 | } 134 | 135 | private var progress: Double { 136 | guard totalDuration > .seconds(0) else { return 0 } 137 | return Double(secondsElapsed) / Double(totalDuration.components.seconds) 138 | } 139 | } 140 | 141 | struct MeetingProgressViewStyle: ProgressViewStyle { 142 | var theme: Theme 143 | 144 | func makeBody(configuration: Configuration) -> some View { 145 | ZStack { 146 | RoundedRectangle(cornerRadius: 10) 147 | .fill(theme.accentColor) 148 | .frame(height: 20) 149 | 150 | ProgressView(configuration) 151 | .tint(theme.mainColor) 152 | .frame(height: 12) 153 | .padding(.horizontal) 154 | } 155 | } 156 | } 157 | 158 | struct MeetingTimerView: View { 159 | let syncUp: SyncUp 160 | let speakerIndex: Int 161 | 162 | var body: some View { 163 | Circle() 164 | .strokeBorder(lineWidth: 24) 165 | .overlay { 166 | VStack { 167 | Group { 168 | if speakerIndex < syncUp.attendees.count { 169 | Text(syncUp.attendees[speakerIndex].name) 170 | } else { 171 | Text("Someone") 172 | } 173 | } 174 | .font(.title) 175 | Text("is speaking") 176 | Image(systemName: "mic.fill") 177 | .font(.largeTitle) 178 | .padding(.top) 179 | } 180 | .foregroundStyle(syncUp.theme.accentColor) 181 | } 182 | .overlay { 183 | ForEach(Array(syncUp.attendees.enumerated()), id: \.element.id) { index, attendee in 184 | if index < speakerIndex + 1 { 185 | SpeakerArc(totalSpeakers: syncUp.attendees.count, speakerIndex: index) 186 | .rotation(Angle(degrees: -90)) 187 | .stroke(syncUp.theme.mainColor, lineWidth: 12) 188 | } 189 | } 190 | } 191 | .padding(.horizontal) 192 | } 193 | } 194 | 195 | struct SpeakerArc: Shape { 196 | let totalSpeakers: Int 197 | let speakerIndex: Int 198 | 199 | func path(in rect: CGRect) -> Path { 200 | let diameter = min(rect.size.width, rect.size.height) - 24 201 | let radius = diameter / 2 202 | let center = CGPoint(x: rect.midX, y: rect.midY) 203 | return Path { path in 204 | path.addArc( 205 | center: center, 206 | radius: radius, 207 | startAngle: startAngle, 208 | endAngle: endAngle, 209 | clockwise: false 210 | ) 211 | } 212 | } 213 | 214 | private var degreesPerSpeaker: Double { 215 | 360 / Double(totalSpeakers) 216 | } 217 | private var startAngle: Angle { 218 | Angle(degrees: degreesPerSpeaker * Double(speakerIndex) + 1) 219 | } 220 | private var endAngle: Angle { 221 | Angle(degrees: startAngle.degrees + degreesPerSpeaker - 1) 222 | } 223 | } 224 | 225 | struct MeetingFooterView: View { 226 | let syncUp: SyncUp 227 | var nextButtonTapped: () -> Void 228 | let speakerIndex: Int 229 | 230 | var body: some View { 231 | VStack { 232 | HStack { 233 | if speakerIndex < syncUp.attendees.count - 1 { 234 | Text("Speaker \(speakerIndex + 1) of \(syncUp.attendees.count)") 235 | } else { 236 | Text("No more speakers.") 237 | } 238 | Spacer() 239 | Button(action: nextButtonTapped) { 240 | Image(systemName: "forward.fill") 241 | } 242 | } 243 | } 244 | .padding([.bottom, .horizontal]) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/SyncUpDetail.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct SyncUpDetailFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | @Presents var alert: AlertState? 9 | @Presents var editSyncUp: SyncUpFormFeature.State? 10 | @Presents var recordMeeting: RecordMeetingFeature.State? 11 | @Shared var syncUp: SyncUp 12 | } 13 | enum Action { 14 | case alert(PresentationAction) 15 | case cancelEditSyncUpButtonTapped 16 | case confirmSaveSyncUpButtonTapped 17 | case deleteButtonTapped 18 | case editButtonTapped 19 | case editSyncUp(PresentationAction) 20 | case recordMeeting(PresentationAction) 21 | case startMeetingButtonTapped 22 | enum Alert { 23 | case confirmDelete 24 | } 25 | } 26 | var body: some ReducerOf { 27 | Reduce { state, action in 28 | switch action { 29 | case .alert(.presented(.confirmDelete)): 30 | // ???? 31 | return .none 32 | case .alert: 33 | return .none 34 | case .cancelEditSyncUpButtonTapped: 35 | state.editSyncUp = nil 36 | return .none 37 | case .confirmSaveSyncUpButtonTapped: 38 | guard let syncUp = state.editSyncUp?.syncUp 39 | else { return .none } 40 | state.syncUp = syncUp 41 | state.editSyncUp = nil 42 | return .none 43 | case .deleteButtonTapped: 44 | state.alert = AlertState { 45 | TextState("Are you sure you want to delete this sync up?") 46 | } actions: { 47 | ButtonState(action: .confirmDelete) { 48 | TextState("Yes") 49 | } 50 | ButtonState(role: .cancel) { 51 | TextState("Nevermind") 52 | } 53 | } 54 | return .none 55 | case .editButtonTapped: 56 | state.editSyncUp = SyncUpFormFeature.State(syncUp: state.syncUp) 57 | return .none 58 | case .editSyncUp: 59 | return .none 60 | case .recordMeeting: 61 | return .none 62 | case .startMeetingButtonTapped: 63 | state.recordMeeting = RecordMeetingFeature.State(syncUp: state.syncUp) 64 | return .none 65 | } 66 | } 67 | .ifLet(\.$alert, action: \.alert) 68 | .ifLet(\.$editSyncUp, action: \.editSyncUp) { 69 | SyncUpFormFeature() 70 | } 71 | .ifLet(\.$recordMeeting, action: \.recordMeeting) { 72 | RecordMeetingFeature() 73 | } 74 | } 75 | } 76 | 77 | struct SyncUpDetailView: View { 78 | @Bindable var store: StoreOf 79 | 80 | var body: some View { 81 | Form { 82 | Section { 83 | Button { 84 | store.send(.startMeetingButtonTapped) 85 | } label: { 86 | Label("Start Meeting", systemImage: "timer") 87 | .font(.headline) 88 | .foregroundColor(.accentColor) 89 | } 90 | HStack { 91 | Label("Length", systemImage: "clock") 92 | Spacer() 93 | Text(store.syncUp.duration.formatted(.units())) 94 | } 95 | 96 | HStack { 97 | Label("Theme", systemImage: "paintpalette") 98 | Spacer() 99 | Text(store.syncUp.theme.name) 100 | .padding(4) 101 | .foregroundColor(store.syncUp.theme.accentColor) 102 | .background(store.syncUp.theme.mainColor) 103 | .cornerRadius(4) 104 | } 105 | } header: { 106 | Text("Sync-up Info") 107 | } 108 | 109 | if store.syncUp.meetings.isEmpty == false { 110 | Section { 111 | ForEach(store.syncUp.meetings) { meeting in 112 | NavigationLink { 113 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 114 | } label: { 115 | HStack { 116 | Image(systemName: "calendar") 117 | Text(meeting.date, style: .date) 118 | Text(meeting.date, style: .time) 119 | } 120 | } 121 | } 122 | .onDelete { indices in 123 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 124 | } 125 | } header: { 126 | Text("Past meetings") 127 | } 128 | } 129 | 130 | Section { 131 | ForEach(store.syncUp.attendees) { attendee in 132 | Label(attendee.name, systemImage: "person") 133 | } 134 | } header: { 135 | Text("Attendees") 136 | } 137 | 138 | Section { 139 | Button("Delete") { 140 | store.send(.deleteButtonTapped) 141 | } 142 | .foregroundColor(.red) 143 | .frame(maxWidth: .infinity) 144 | } 145 | } 146 | .toolbar { 147 | Button("Edit") { 148 | store.send(.editButtonTapped) 149 | } 150 | } 151 | .navigationTitle(Text(store.syncUp.title)) 152 | .alert($store.scope(state: \.alert, action: \.alert)) 153 | .sheet(item: $store.scope(state: \.editSyncUp, action: \.editSyncUp)) { formStore in 154 | NavigationStack { 155 | SyncUpFormView(store: formStore) 156 | .navigationTitle(Text("Edit sync up")) 157 | .toolbar { 158 | ToolbarItem { 159 | Button("Save") { 160 | store.send(.confirmSaveSyncUpButtonTapped) 161 | } 162 | } 163 | ToolbarItem(placement: .cancellationAction) { 164 | Button("Cancel") { 165 | store.send(.cancelEditSyncUpButtonTapped) 166 | } 167 | } 168 | } 169 | } 170 | } 171 | .sheet(item: $store.scope(state: \.recordMeeting, action: \.recordMeeting)) { recordStore in 172 | NavigationStack { 173 | RecordMeetingView(store: recordStore) 174 | } 175 | } 176 | } 177 | } 178 | 179 | #Preview { 180 | NavigationStack { 181 | SyncUpDetailView( 182 | store: Store(initialState: SyncUpDetailFeature.State(syncUp: Shared(.mock))) { 183 | SyncUpDetailFeature() 184 | ._printChanges() 185 | } 186 | ) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/SyncUpForm.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct SyncUpFormFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | var syncUp: SyncUp 9 | } 10 | enum Action: BindableAction { 11 | case addAttendeeButtonTapped 12 | case binding(BindingAction) 13 | case onDeleteAttendees(IndexSet) 14 | } 15 | var body: some ReducerOf { 16 | BindingReducer() 17 | Reduce { state, action in 18 | switch action { 19 | case .addAttendeeButtonTapped: 20 | state.syncUp.attendees.append(Attendee(id: UUID())) 21 | return .none 22 | case .binding: 23 | return .none 24 | case let .onDeleteAttendees(indexSet): 25 | state.syncUp.attendees.remove(atOffsets: indexSet) 26 | return .none 27 | } 28 | } 29 | } 30 | } 31 | 32 | struct SyncUpFormView: View { 33 | @Bindable var store: StoreOf 34 | 35 | var body: some View { 36 | Form { 37 | Section { 38 | TextField("Title", text: $store.syncUp.title) 39 | HStack { 40 | Slider(value: $store.syncUp.duration.minutes, in: 5...30, step: 1) { 41 | Text("Length") 42 | } 43 | Spacer() 44 | Text(store.syncUp.duration.formatted(.units())) 45 | } 46 | ThemePicker(selection: $store.syncUp.theme) 47 | } header: { 48 | Text("Sync-up Info") 49 | } 50 | Section { 51 | ForEach($store.syncUp.attendees) { $attendee in 52 | TextField("Name", text: $attendee.name) 53 | } 54 | .onDelete { indices in 55 | store.send(.onDeleteAttendees(indices)) 56 | } 57 | 58 | Button("New attendee") { 59 | store.send(.addAttendeeButtonTapped) 60 | } 61 | } header: { 62 | Text("Attendees") 63 | } 64 | } 65 | } 66 | } 67 | 68 | 69 | #Preview { 70 | NavigationStack { 71 | SyncUpFormView( 72 | store: Store(initialState: SyncUpFormFeature.State(syncUp: .mock)) { 73 | SyncUpFormFeature() 74 | } 75 | ) 76 | } 77 | } 78 | 79 | struct ThemePicker: View { 80 | @Binding var selection: Theme 81 | 82 | var body: some View { 83 | Picker("Theme", selection: $selection) { 84 | ForEach(Theme.allCases) { theme in 85 | ZStack { 86 | RoundedRectangle(cornerRadius: 4) 87 | .fill(theme.mainColor) 88 | Label(theme.name, systemImage: "paintpalette") 89 | .padding(4) 90 | } 91 | .foregroundColor(theme.accentColor) 92 | .fixedSize(horizontal: false, vertical: true) 93 | .tag(theme) 94 | } 95 | } 96 | } 97 | } 98 | 99 | extension Duration { 100 | fileprivate var minutes: Double { 101 | get { Double(components.seconds / 60) } 102 | set { self = .seconds(newValue * 60) } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /complete-project/TrySyncUps/SyncUpsList.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | extension URL { 5 | static let syncUps = URL.documentsDirectory.appending(path: "sync-ups.json") 6 | } 7 | 8 | @Reducer 9 | struct SyncUpsListFeature { 10 | @ObservableState 11 | struct State: Equatable { 12 | @Presents var addSyncUp: SyncUpFormFeature.State? 13 | @Presents var syncUpDetail: SyncUpDetailFeature.State? 14 | @Shared(.fileStorage(.syncUps)) var syncUps: IdentifiedArrayOf = [] 15 | } 16 | enum Action { 17 | case addSyncUp(PresentationAction) 18 | case addSyncUpButtonTapped 19 | case cancelAddSyncUpButtonTapped 20 | case confirmAddSyncUpButtonTapped 21 | case onDelete(IndexSet) 22 | case syncUpDetail(PresentationAction) 23 | case syncUpTapped(id: SyncUp.ID) 24 | } 25 | @Dependency(\.uuid) var uuid 26 | var body: some ReducerOf { 27 | Reduce { state, action in 28 | switch action { 29 | case .addSyncUp: 30 | return .none 31 | 32 | case .addSyncUpButtonTapped: 33 | state.addSyncUp = SyncUpFormFeature.State(syncUp: SyncUp(id: uuid())) 34 | return .none 35 | 36 | case .cancelAddSyncUpButtonTapped: 37 | state.addSyncUp = nil 38 | return .none 39 | 40 | case .confirmAddSyncUpButtonTapped: 41 | guard let syncUp = state.addSyncUp?.syncUp 42 | else { return .none } 43 | state.syncUps.append(syncUp) 44 | state.addSyncUp = nil 45 | return .none 46 | 47 | case let .onDelete(indexSet): 48 | state.syncUps.remove(atOffsets: indexSet) 49 | return .none 50 | 51 | case .syncUpDetail: 52 | return .none 53 | 54 | case let .syncUpTapped(id: id): 55 | guard let syncUp = state.$syncUps[id: id] 56 | else { return .none } 57 | state.syncUpDetail = SyncUpDetailFeature.State(syncUp: syncUp) 58 | return .none 59 | } 60 | } 61 | .ifLet(\.$addSyncUp, action: \.addSyncUp) { 62 | SyncUpFormFeature() 63 | } 64 | .ifLet(\.$syncUpDetail, action: \.syncUpDetail) { 65 | SyncUpDetailFeature() 66 | } 67 | } 68 | } 69 | 70 | @Observable 71 | class SyncUpsModel { 72 | var syncUps: IdentifiedArrayOf = [] 73 | func onDelete(_ indexSet: IndexSet) { 74 | } 75 | } 76 | 77 | struct SyncUpsListView: View { 78 | @Bindable var store: StoreOf 79 | //let model: SyncUpsModel 80 | 81 | var body: some View { 82 | List { 83 | ForEach(store.syncUps) { syncUp in 84 | Button { 85 | store.send(.syncUpTapped(id: syncUp.id)) 86 | } label: { 87 | CardView(syncUp: syncUp) 88 | } 89 | .listRowBackground(syncUp.theme.mainColor) 90 | } 91 | .onDelete { indexSet in 92 | store.send(.onDelete(indexSet)) 93 | //model.onDelete(indexSet) 94 | } 95 | } 96 | .toolbar { 97 | Button { 98 | store.send(.addSyncUpButtonTapped) 99 | } label: { 100 | Image(systemName: "plus") 101 | } 102 | } 103 | .navigationTitle("Daily Sync-ups") 104 | .sheet(item: $store.scope(state: \.addSyncUp, action: \.addSyncUp)) { formStore in 105 | NavigationStack { 106 | SyncUpFormView(store: formStore) 107 | .navigationTitle("New sync up") 108 | .toolbar { 109 | ToolbarItem { 110 | Button("Add") { store.send(.confirmAddSyncUpButtonTapped) } 111 | } 112 | ToolbarItem(placement: .cancellationAction) { 113 | Button("Cancel") { store.send(.cancelAddSyncUpButtonTapped) } 114 | } 115 | } 116 | } 117 | } 118 | .navigationDestination(item: $store.scope(state: \.syncUpDetail, action: \.syncUpDetail)) { detailStore in 119 | SyncUpDetailView(store: detailStore) 120 | } 121 | } 122 | } 123 | 124 | #Preview { 125 | NavigationStack { 126 | SyncUpsListView( 127 | store: Store( 128 | initialState: SyncUpsListFeature.State( 129 | //addSyncUp: SyncUpFormFeature.State(syncUp: .mock), 130 | syncUps: [.mock, .productMock, .engineeringMock] 131 | ) 132 | ) { 133 | SyncUpsListFeature()._printChanges() 134 | } 135 | ) 136 | } 137 | } 138 | 139 | struct CardView: View { 140 | let syncUp: SyncUp 141 | 142 | var body: some View { 143 | VStack(alignment: .leading) { 144 | Text(syncUp.title) 145 | .font(.headline) 146 | Spacer() 147 | HStack { 148 | Label("\(syncUp.attendees.count)", systemImage: "person.3") 149 | Spacer() 150 | Label(syncUp.duration.formatted(.units()), systemImage: "clock") 151 | .labelStyle(.trailingIcon) 152 | } 153 | .font(.caption) 154 | } 155 | .padding() 156 | .foregroundColor(syncUp.theme.accentColor) 157 | } 158 | } 159 | 160 | struct TrailingIconLabelStyle: LabelStyle { 161 | func makeBody(configuration: Configuration) -> some View { 162 | HStack { 163 | configuration.title 164 | configuration.icon 165 | } 166 | } 167 | } 168 | 169 | extension LabelStyle where Self == TrailingIconLabelStyle { 170 | static var trailingIcon: Self { Self() } 171 | } 172 | -------------------------------------------------------------------------------- /complete-project/TrySyncUpsTests/RecordMeetingsTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class RecordMeetingTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /complete-project/TrySyncUpsTests/SyncUpDetailTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpDetailTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /complete-project/TrySyncUpsTests/SyncUpFormTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpFormTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /complete-project/TrySyncUpsTests/SyncUpsListTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpsListsTests: XCTestCase { 7 | @MainActor 8 | func testDelete() async { 9 | let store = TestStore( 10 | initialState: SyncUpsListFeature.State( 11 | syncUps: [.mock, .engineeringMock] 12 | ) 13 | ) { 14 | SyncUpsListFeature() 15 | } 16 | 17 | await store.send(.onDelete([1])) { 18 | $0.syncUps.remove(at: 1) 19 | } 20 | } 21 | 22 | @MainActor 23 | func testAddSyncUp() async { 24 | let store = TestStore(initialState: SyncUpsListFeature.State()) { 25 | SyncUpsListFeature() 26 | } withDependencies: { 27 | $0.uuid = .incrementing 28 | } 29 | 30 | await store.send(.addSyncUpButtonTapped) { 31 | $0.addSyncUp = SyncUpFormFeature.State(syncUp: SyncUp(id: UUID(0))) 32 | } 33 | await store.send(\.addSyncUp.binding.syncUp, SyncUp(id: UUID(0), title: "Morning")) { 34 | $0.addSyncUp?.syncUp.title = "Morning" 35 | } 36 | await store.send(.confirmAddSyncUpButtonTapped) { 37 | $0.addSyncUp = nil 38 | $0.syncUps = [ 39 | SyncUp(id: UUID(0), title: "Morning") 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DC531C482BA3CF1E005C7270 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C472BA3CF1E005C7270 /* App.swift */; }; 11 | DC531C4C2BA3CF20005C7270 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC531C4B2BA3CF20005C7270 /* Assets.xcassets */; }; 12 | DC531C4F2BA3CF20005C7270 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC531C4E2BA3CF20005C7270 /* Preview Assets.xcassets */; }; 13 | DC531C592BA3CF20005C7270 /* RecordMeetingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C582BA3CF20005C7270 /* RecordMeetingsTests.swift */; }; 14 | DC531C732BA3CF4F005C7270 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = DC531C722BA3CF4F005C7270 /* ComposableArchitecture */; }; 15 | DC531C752BA3CFA1005C7270 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C742BA3CFA1005C7270 /* Models.swift */; }; 16 | DC531C772BA3D014005C7270 /* SyncUpsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C762BA3D014005C7270 /* SyncUpsList.swift */; }; 17 | DC531C792BA3D420005C7270 /* SyncUpForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C782BA3D420005C7270 /* SyncUpForm.swift */; }; 18 | DC531C7B2BA3D649005C7270 /* SyncUpDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C7A2BA3D649005C7270 /* SyncUpDetail.swift */; }; 19 | DC531C7D2BA3D84A005C7270 /* Meeting.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C7C2BA3D84A005C7270 /* Meeting.swift */; }; 20 | DC531C7F2BA3D8C1005C7270 /* RecordMeeting.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C7E2BA3D8C1005C7270 /* RecordMeeting.swift */; }; 21 | DC531C812BA426C1005C7270 /* SyncUpDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C802BA426C1005C7270 /* SyncUpDetailTests.swift */; }; 22 | DC531C832BA426CC005C7270 /* SyncUpFormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C822BA426CC005C7270 /* SyncUpFormTests.swift */; }; 23 | DC531C852BA426DB005C7270 /* SyncUpsListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC531C842BA426DB005C7270 /* SyncUpsListTests.swift */; }; 24 | DCB5B1472BAF9611002DA9E4 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = DCB5B1462BAF9611002DA9E4 /* README.md */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | DC531C552BA3CF20005C7270 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = DC531C3C2BA3CF1E005C7270 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = DC531C432BA3CF1E005C7270; 33 | remoteInfo = TrySyncUps; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | CA620C752BA7925A006C9105 /* TrySyncUps.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TrySyncUps.xctestplan; sourceTree = ""; }; 39 | DC531C442BA3CF1E005C7270 /* TrySyncUps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TrySyncUps.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | DC531C472BA3CF1E005C7270 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 41 | DC531C4B2BA3CF20005C7270 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | DC531C4E2BA3CF20005C7270 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 43 | DC531C542BA3CF20005C7270 /* TrySyncUpsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TrySyncUpsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | DC531C582BA3CF20005C7270 /* RecordMeetingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordMeetingsTests.swift; sourceTree = ""; }; 45 | DC531C742BA3CFA1005C7270 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 46 | DC531C762BA3D014005C7270 /* SyncUpsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpsList.swift; sourceTree = ""; }; 47 | DC531C782BA3D420005C7270 /* SyncUpForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpForm.swift; sourceTree = ""; }; 48 | DC531C7A2BA3D649005C7270 /* SyncUpDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpDetail.swift; sourceTree = ""; }; 49 | DC531C7C2BA3D84A005C7270 /* Meeting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Meeting.swift; sourceTree = ""; }; 50 | DC531C7E2BA3D8C1005C7270 /* RecordMeeting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordMeeting.swift; sourceTree = ""; }; 51 | DC531C802BA426C1005C7270 /* SyncUpDetailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpDetailTests.swift; sourceTree = ""; }; 52 | DC531C822BA426CC005C7270 /* SyncUpFormTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpFormTests.swift; sourceTree = ""; }; 53 | DC531C842BA426DB005C7270 /* SyncUpsListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpsListTests.swift; sourceTree = ""; }; 54 | DCB5B1462BAF9611002DA9E4 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | DC531C412BA3CF1E005C7270 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | DC531C732BA3CF4F005C7270 /* ComposableArchitecture in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | DC531C512BA3CF20005C7270 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | DC531C3B2BA3CF1E005C7270 = { 77 | isa = PBXGroup; 78 | children = ( 79 | DCB5B1462BAF9611002DA9E4 /* README.md */, 80 | DC531C462BA3CF1E005C7270 /* TrySyncUps */, 81 | DC531C572BA3CF20005C7270 /* TrySyncUpsTests */, 82 | DC531C452BA3CF1E005C7270 /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | DC531C452BA3CF1E005C7270 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | DC531C442BA3CF1E005C7270 /* TrySyncUps.app */, 90 | DC531C542BA3CF20005C7270 /* TrySyncUpsTests.xctest */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | DC531C462BA3CF1E005C7270 /* TrySyncUps */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | DC531C472BA3CF1E005C7270 /* App.swift */, 99 | DC531C7C2BA3D84A005C7270 /* Meeting.swift */, 100 | DC531C742BA3CFA1005C7270 /* Models.swift */, 101 | DC531C7E2BA3D8C1005C7270 /* RecordMeeting.swift */, 102 | DC531C7A2BA3D649005C7270 /* SyncUpDetail.swift */, 103 | DC531C782BA3D420005C7270 /* SyncUpForm.swift */, 104 | DC531C762BA3D014005C7270 /* SyncUpsList.swift */, 105 | DC531C4B2BA3CF20005C7270 /* Assets.xcassets */, 106 | DC531C4D2BA3CF20005C7270 /* Preview Content */, 107 | ); 108 | path = TrySyncUps; 109 | sourceTree = ""; 110 | }; 111 | DC531C4D2BA3CF20005C7270 /* Preview Content */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | DC531C4E2BA3CF20005C7270 /* Preview Assets.xcassets */, 115 | ); 116 | path = "Preview Content"; 117 | sourceTree = ""; 118 | }; 119 | DC531C572BA3CF20005C7270 /* TrySyncUpsTests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | CA620C752BA7925A006C9105 /* TrySyncUps.xctestplan */, 123 | DC531C582BA3CF20005C7270 /* RecordMeetingsTests.swift */, 124 | DC531C802BA426C1005C7270 /* SyncUpDetailTests.swift */, 125 | DC531C822BA426CC005C7270 /* SyncUpFormTests.swift */, 126 | DC531C842BA426DB005C7270 /* SyncUpsListTests.swift */, 127 | ); 128 | path = TrySyncUpsTests; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | DC531C432BA3CF1E005C7270 /* TrySyncUps */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = DC531C682BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUps" */; 137 | buildPhases = ( 138 | DC531C402BA3CF1E005C7270 /* Sources */, 139 | DC531C412BA3CF1E005C7270 /* Frameworks */, 140 | DC531C422BA3CF1E005C7270 /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = TrySyncUps; 147 | packageProductDependencies = ( 148 | DC531C722BA3CF4F005C7270 /* ComposableArchitecture */, 149 | ); 150 | productName = TrySyncUps; 151 | productReference = DC531C442BA3CF1E005C7270 /* TrySyncUps.app */; 152 | productType = "com.apple.product-type.application"; 153 | }; 154 | DC531C532BA3CF20005C7270 /* TrySyncUpsTests */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = DC531C6B2BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUpsTests" */; 157 | buildPhases = ( 158 | DC531C502BA3CF20005C7270 /* Sources */, 159 | DC531C512BA3CF20005C7270 /* Frameworks */, 160 | DC531C522BA3CF20005C7270 /* Resources */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | DC531C562BA3CF20005C7270 /* PBXTargetDependency */, 166 | ); 167 | name = TrySyncUpsTests; 168 | productName = TrySyncUpsTests; 169 | productReference = DC531C542BA3CF20005C7270 /* TrySyncUpsTests.xctest */; 170 | productType = "com.apple.product-type.bundle.unit-test"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | DC531C3C2BA3CF1E005C7270 /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | BuildIndependentTargetsInParallel = 1; 179 | LastSwiftUpdateCheck = 1530; 180 | LastUpgradeCheck = 1530; 181 | TargetAttributes = { 182 | DC531C432BA3CF1E005C7270 = { 183 | CreatedOnToolsVersion = 15.3; 184 | }; 185 | DC531C532BA3CF20005C7270 = { 186 | CreatedOnToolsVersion = 15.3; 187 | TestTargetID = DC531C432BA3CF1E005C7270; 188 | }; 189 | }; 190 | }; 191 | buildConfigurationList = DC531C3F2BA3CF1E005C7270 /* Build configuration list for PBXProject "TrySyncUps" */; 192 | compatibilityVersion = "Xcode 14.0"; 193 | developmentRegion = en; 194 | hasScannedForEncodings = 0; 195 | knownRegions = ( 196 | en, 197 | Base, 198 | ); 199 | mainGroup = DC531C3B2BA3CF1E005C7270; 200 | packageReferences = ( 201 | DC531C712BA3CF4F005C7270 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, 202 | ); 203 | productRefGroup = DC531C452BA3CF1E005C7270 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | DC531C432BA3CF1E005C7270 /* TrySyncUps */, 208 | DC531C532BA3CF20005C7270 /* TrySyncUpsTests */, 209 | ); 210 | }; 211 | /* End PBXProject section */ 212 | 213 | /* Begin PBXResourcesBuildPhase section */ 214 | DC531C422BA3CF1E005C7270 /* Resources */ = { 215 | isa = PBXResourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | DCB5B1472BAF9611002DA9E4 /* README.md in Resources */, 219 | DC531C4F2BA3CF20005C7270 /* Preview Assets.xcassets in Resources */, 220 | DC531C4C2BA3CF20005C7270 /* Assets.xcassets in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | DC531C522BA3CF20005C7270 /* Resources */ = { 225 | isa = PBXResourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXResourcesBuildPhase section */ 232 | 233 | /* Begin PBXSourcesBuildPhase section */ 234 | DC531C402BA3CF1E005C7270 /* Sources */ = { 235 | isa = PBXSourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | DC531C752BA3CFA1005C7270 /* Models.swift in Sources */, 239 | DC531C7D2BA3D84A005C7270 /* Meeting.swift in Sources */, 240 | DC531C7B2BA3D649005C7270 /* SyncUpDetail.swift in Sources */, 241 | DC531C792BA3D420005C7270 /* SyncUpForm.swift in Sources */, 242 | DC531C7F2BA3D8C1005C7270 /* RecordMeeting.swift in Sources */, 243 | DC531C772BA3D014005C7270 /* SyncUpsList.swift in Sources */, 244 | DC531C482BA3CF1E005C7270 /* App.swift in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | DC531C502BA3CF20005C7270 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | DC531C812BA426C1005C7270 /* SyncUpDetailTests.swift in Sources */, 253 | DC531C852BA426DB005C7270 /* SyncUpsListTests.swift in Sources */, 254 | DC531C832BA426CC005C7270 /* SyncUpFormTests.swift in Sources */, 255 | DC531C592BA3CF20005C7270 /* RecordMeetingsTests.swift in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXSourcesBuildPhase section */ 260 | 261 | /* Begin PBXTargetDependency section */ 262 | DC531C562BA3CF20005C7270 /* PBXTargetDependency */ = { 263 | isa = PBXTargetDependency; 264 | target = DC531C432BA3CF1E005C7270 /* TrySyncUps */; 265 | targetProxy = DC531C552BA3CF20005C7270 /* PBXContainerItemProxy */; 266 | }; 267 | /* End PBXTargetDependency section */ 268 | 269 | /* Begin XCBuildConfiguration section */ 270 | DC531C662BA3CF20005C7270 /* Debug */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | ALWAYS_SEARCH_USER_PATHS = NO; 274 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 275 | CLANG_ANALYZER_NONNULL = YES; 276 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 277 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 278 | CLANG_ENABLE_MODULES = YES; 279 | CLANG_ENABLE_OBJC_ARC = YES; 280 | CLANG_ENABLE_OBJC_WEAK = YES; 281 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 282 | CLANG_WARN_BOOL_CONVERSION = YES; 283 | CLANG_WARN_COMMA = YES; 284 | CLANG_WARN_CONSTANT_CONVERSION = YES; 285 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 286 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 287 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 288 | CLANG_WARN_EMPTY_BODY = YES; 289 | CLANG_WARN_ENUM_CONVERSION = YES; 290 | CLANG_WARN_INFINITE_RECURSION = YES; 291 | CLANG_WARN_INT_CONVERSION = YES; 292 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 293 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 294 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 295 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 296 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 297 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 298 | CLANG_WARN_STRICT_PROTOTYPES = YES; 299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 300 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | COPY_PHASE_STRIP = NO; 304 | DEBUG_INFORMATION_FORMAT = dwarf; 305 | ENABLE_STRICT_OBJC_MSGSEND = YES; 306 | ENABLE_TESTABILITY = YES; 307 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu17; 309 | GCC_DYNAMIC_NO_PIC = NO; 310 | GCC_NO_COMMON_BLOCKS = YES; 311 | GCC_OPTIMIZATION_LEVEL = 0; 312 | GCC_PREPROCESSOR_DEFINITIONS = ( 313 | "DEBUG=1", 314 | "$(inherited)", 315 | ); 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 318 | GCC_WARN_UNDECLARED_SELECTOR = YES; 319 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 320 | GCC_WARN_UNUSED_FUNCTION = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 323 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 324 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 325 | MTL_FAST_MATH = YES; 326 | ONLY_ACTIVE_ARCH = YES; 327 | SDKROOT = iphoneos; 328 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 329 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 330 | }; 331 | name = Debug; 332 | }; 333 | DC531C672BA3CF20005C7270 /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_ENABLE_OBJC_WEAK = YES; 344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 345 | CLANG_WARN_BOOL_CONVERSION = YES; 346 | CLANG_WARN_COMMA = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 349 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 350 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 361 | CLANG_WARN_STRICT_PROTOTYPES = YES; 362 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 363 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | COPY_PHASE_STRIP = NO; 367 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 368 | ENABLE_NS_ASSERTIONS = NO; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu17; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 380 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 381 | MTL_ENABLE_DEBUG_INFO = NO; 382 | MTL_FAST_MATH = YES; 383 | SDKROOT = iphoneos; 384 | SWIFT_COMPILATION_MODE = wholemodule; 385 | VALIDATE_PRODUCT = YES; 386 | }; 387 | name = Release; 388 | }; 389 | DC531C692BA3CF20005C7270 /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 393 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 394 | CODE_SIGN_STYLE = Automatic; 395 | CURRENT_PROJECT_VERSION = 1; 396 | DEVELOPMENT_ASSET_PATHS = "\"TrySyncUps/Preview Content\""; 397 | ENABLE_PREVIEWS = YES; 398 | GENERATE_INFOPLIST_FILE = YES; 399 | INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "To transcribe meeting notes."; 400 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 401 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 402 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 403 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 404 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 405 | LD_RUNPATH_SEARCH_PATHS = ( 406 | "$(inherited)", 407 | "@executable_path/Frameworks", 408 | ); 409 | MARKETING_VERSION = 1.0; 410 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUps; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_EMIT_LOC_STRINGS = YES; 413 | SWIFT_VERSION = 5.0; 414 | TARGETED_DEVICE_FAMILY = "1,2"; 415 | }; 416 | name = Debug; 417 | }; 418 | DC531C6A2BA3CF20005C7270 /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 422 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 423 | CODE_SIGN_STYLE = Automatic; 424 | CURRENT_PROJECT_VERSION = 1; 425 | DEVELOPMENT_ASSET_PATHS = "\"TrySyncUps/Preview Content\""; 426 | ENABLE_PREVIEWS = YES; 427 | GENERATE_INFOPLIST_FILE = YES; 428 | INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "To transcribe meeting notes."; 429 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 430 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 431 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 432 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 433 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 434 | LD_RUNPATH_SEARCH_PATHS = ( 435 | "$(inherited)", 436 | "@executable_path/Frameworks", 437 | ); 438 | MARKETING_VERSION = 1.0; 439 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUps; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | SWIFT_EMIT_LOC_STRINGS = YES; 442 | SWIFT_VERSION = 5.0; 443 | TARGETED_DEVICE_FAMILY = "1,2"; 444 | }; 445 | name = Release; 446 | }; 447 | DC531C6C2BA3CF20005C7270 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 451 | BUNDLE_LOADER = "$(TEST_HOST)"; 452 | CODE_SIGN_STYLE = Automatic; 453 | CURRENT_PROJECT_VERSION = 1; 454 | GENERATE_INFOPLIST_FILE = YES; 455 | MARKETING_VERSION = 1.0; 456 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUpsTests; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | SWIFT_EMIT_LOC_STRINGS = NO; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TrySyncUps.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TrySyncUps"; 462 | }; 463 | name = Debug; 464 | }; 465 | DC531C6D2BA3CF20005C7270 /* Release */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 469 | BUNDLE_LOADER = "$(TEST_HOST)"; 470 | CODE_SIGN_STYLE = Automatic; 471 | CURRENT_PROJECT_VERSION = 1; 472 | GENERATE_INFOPLIST_FILE = YES; 473 | MARKETING_VERSION = 1.0; 474 | PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.TrySyncUpsTests; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_EMIT_LOC_STRINGS = NO; 477 | SWIFT_VERSION = 5.0; 478 | TARGETED_DEVICE_FAMILY = "1,2"; 479 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TrySyncUps.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TrySyncUps"; 480 | }; 481 | name = Release; 482 | }; 483 | /* End XCBuildConfiguration section */ 484 | 485 | /* Begin XCConfigurationList section */ 486 | DC531C3F2BA3CF1E005C7270 /* Build configuration list for PBXProject "TrySyncUps" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | DC531C662BA3CF20005C7270 /* Debug */, 490 | DC531C672BA3CF20005C7270 /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | DC531C682BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUps" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | DC531C692BA3CF20005C7270 /* Debug */, 499 | DC531C6A2BA3CF20005C7270 /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | DC531C6B2BA3CF20005C7270 /* Build configuration list for PBXNativeTarget "TrySyncUpsTests" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | DC531C6C2BA3CF20005C7270 /* Debug */, 508 | DC531C6D2BA3CF20005C7270 /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | /* End XCConfigurationList section */ 514 | 515 | /* Begin XCRemoteSwiftPackageReference section */ 516 | DC531C712BA3CF4F005C7270 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { 517 | isa = XCRemoteSwiftPackageReference; 518 | repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture.git"; 519 | requirement = { 520 | branch = "shared-state-beta"; 521 | kind = branch; 522 | }; 523 | }; 524 | /* End XCRemoteSwiftPackageReference section */ 525 | 526 | /* Begin XCSwiftPackageProductDependency section */ 527 | DC531C722BA3CF4F005C7270 /* ComposableArchitecture */ = { 528 | isa = XCSwiftPackageProductDependency; 529 | package = DC531C712BA3CF4F005C7270 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; 530 | productName = ComposableArchitecture; 531 | }; 532 | /* End XCSwiftPackageProductDependency section */ 533 | }; 534 | rootObject = DC531C3C2BA3CF1E005C7270 /* Project object */; 535 | } 536 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "81d703dd6e7ef361d922e4159d56e99fa7337762368097761b4ba0ff022b4d31", 3 | "pins" : [ 4 | { 5 | "identity" : "combine-schedulers", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/pointfreeco/combine-schedulers", 8 | "state" : { 9 | "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", 10 | "version" : "1.0.0" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-case-paths", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/pointfreeco/swift-case-paths", 17 | "state" : { 18 | "revision" : "e593aba2c6222daad7c4f2732a431eed2c09bb07", 19 | "version" : "1.3.0" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-clocks", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/pointfreeco/swift-clocks", 26 | "state" : { 27 | "revision" : "a8421d68068d8f45fbceb418fbf22c5dad4afd33", 28 | "version" : "1.0.2" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-collections", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/apple/swift-collections", 35 | "state" : { 36 | "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", 37 | "version" : "1.1.0" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-composable-architecture", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", 44 | "state" : { 45 | "branch" : "shared-state-beta", 46 | "revision" : "ad00d06a272f31cd2a409f7f683f4af4c71d90c4" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-concurrency-extras", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/pointfreeco/swift-concurrency-extras", 53 | "state" : { 54 | "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", 55 | "version" : "1.1.0" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-custom-dump", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 62 | "state" : { 63 | "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", 64 | "version" : "1.3.0" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-dependencies", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/pointfreeco/swift-dependencies", 71 | "state" : { 72 | "revision" : "d3a5af3038a09add4d7682f66555d6212058a3c0", 73 | "version" : "1.2.2" 74 | } 75 | }, 76 | { 77 | "identity" : "swift-identified-collections", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/pointfreeco/swift-identified-collections", 80 | "state" : { 81 | "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", 82 | "version" : "1.0.0" 83 | } 84 | }, 85 | { 86 | "identity" : "swift-perception", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/pointfreeco/swift-perception", 89 | "state" : { 90 | "revision" : "a5bb578d963fcdbffe4fd56c92b2e222f5b02c8a", 91 | "version" : "1.1.2" 92 | } 93 | }, 94 | { 95 | "identity" : "swift-syntax", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/apple/swift-syntax", 98 | "state" : { 99 | "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", 100 | "version" : "510.0.1" 101 | } 102 | }, 103 | { 104 | "identity" : "swiftui-navigation", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/pointfreeco/swiftui-navigation", 107 | "state" : { 108 | "revision" : "d9e72f3083c08375794afa216fb2f89c0114f303", 109 | "version" : "1.2.1" 110 | } 111 | }, 112 | { 113 | "identity" : "xctest-dynamic-overlay", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 116 | "state" : { 117 | "revision" : "b13b1d1a8e787a5ffc71ac19dcaf52183ab27ba2", 118 | "version" : "1.1.1" 119 | } 120 | } 121 | ], 122 | "version" : 3 123 | } 124 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps.xcodeproj/xcshareddata/xcschemes/TrySyncUps.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 35 | 36 | 37 | 38 | 40 | 46 | 47 | 48 | 49 | 50 | 60 | 62 | 68 | 69 | 70 | 71 | 77 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/App.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @main 5 | struct SyncUpsApp: App { 6 | var body: some Scene { 7 | WindowGroup { 8 | NavigationStack { 9 | SyncUpsListView( 10 | store: Store( 11 | initialState: SyncUpsListFeature.State( 12 | // destination: .syncUpDetail( 13 | // SyncUpDetailFeature.State( 14 | // //editSyncUp: SyncUpFormFeature.State(syncUp: .mock), 15 | // recordMeeting: RecordMeetingFeature.State(syncUp: .mock), 16 | // syncUp: Shared(.mock) 17 | // ) 18 | // ) 19 | ) 20 | ) { 21 | SyncUpsListFeature() 22 | ._printChanges() 23 | } 24 | ) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/appIndigo.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.443", 9 | "green" : "0.000", 10 | "red" : "0.212" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.443", 27 | "green" : "0.000", 28 | "red" : "0.212" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/appMagenta.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.467", 9 | "green" : "0.075", 10 | "red" : "0.647" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.467", 27 | "green" : "0.075", 28 | "red" : "0.647" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/appOrange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.259", 9 | "green" : "0.545", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.259", 27 | "green" : "0.545", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/appPurple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.949", 9 | "green" : "0.294", 10 | "red" : "0.569" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.949", 27 | "green" : "0.294", 28 | "red" : "0.569" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/appTeal.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.620", 9 | "green" : "0.561", 10 | "red" : "0.133" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.620", 27 | "green" : "0.561", 28 | "red" : "0.133" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/bubblegum.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.820", 9 | "green" : "0.502", 10 | "red" : "0.933" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.820", 27 | "green" : "0.502", 28 | "red" : "0.933" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/buttercup.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.588", 9 | "green" : "0.945", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.588", 27 | "green" : "0.945", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/lavender.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.808", 10 | "red" : "0.812" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.808", 28 | "red" : "0.812" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/navy.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.255", 9 | "green" : "0.078", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.255", 27 | "green" : "0.078", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/oxblood.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.043", 9 | "green" : "0.027", 10 | "red" : "0.290" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.043", 27 | "green" : "0.027", 28 | "red" : "0.290" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/periwinkle.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.510", 10 | "red" : "0.525" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.510", 28 | "red" : "0.525" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/poppy.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.369", 9 | "green" : "0.369", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.369", 27 | "green" : "0.369", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/seafoam.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.898", 9 | "green" : "0.918", 10 | "red" : "0.796" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.898", 27 | "green" : "0.918", 28 | "red" : "0.796" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/sky.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.573", 10 | "red" : "0.431" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.573", 28 | "red" : "0.431" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/tan.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.494", 9 | "green" : "0.608", 10 | "red" : "0.761" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.494", 27 | "green" : "0.608", 28 | "red" : "0.761" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Assets.xcassets/Themes/yellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.302", 9 | "green" : "0.875", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.302", 27 | "green" : "0.875", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Meeting.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MeetingView: View { 4 | let meeting: Meeting 5 | let syncUp: SyncUp 6 | 7 | var body: some View { 8 | Form { 9 | Section { 10 | ForEach(syncUp.attendees) { attendee in 11 | Text(attendee.name) 12 | } 13 | } header: { 14 | Text("Attendees") 15 | } 16 | Section { 17 | Text(meeting.transcript) 18 | } header: { 19 | Text("Transcript") 20 | } 21 | } 22 | .navigationTitle(Text(meeting.date, style: .date)) 23 | } 24 | } 25 | 26 | #Preview { 27 | NavigationStack { 28 | MeetingView(meeting: SyncUp.mock.meetings[0], syncUp: .mock) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Models.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | struct SyncUp: Equatable, Identifiable, Codable { 5 | let id: UUID 6 | var attendees: IdentifiedArrayOf = [] 7 | var duration: Duration = .seconds(60 * 5) 8 | var meetings: IdentifiedArrayOf = [] 9 | var theme: Theme = .bubblegum 10 | var title = "" 11 | 12 | var durationPerAttendee: Duration { 13 | duration / attendees.count 14 | } 15 | } 16 | 17 | struct Attendee: Equatable, Identifiable, Codable { 18 | let id: UUID 19 | var name = "" 20 | } 21 | 22 | struct Meeting: Equatable, Identifiable, Codable { 23 | let id: UUID 24 | let date: Date 25 | var transcript: String 26 | } 27 | 28 | enum Theme: String, CaseIterable, Equatable, Identifiable, Codable { 29 | case appIndigo 30 | case appMagenta 31 | case appOrange 32 | case appPurple 33 | case appTeal 34 | case yellow 35 | case bubblegum 36 | case buttercup 37 | case lavender 38 | case navy 39 | case oxblood 40 | case periwinkle 41 | case poppy 42 | case seafoam 43 | case sky 44 | case tan 45 | 46 | var id: Self { self } 47 | 48 | var accentColor: Color { 49 | switch self { 50 | case .appOrange, .appTeal, .yellow, .bubblegum, .buttercup, .lavender, .periwinkle, .poppy, 51 | .seafoam, .sky, .tan: 52 | return .black 53 | case .appIndigo, .appMagenta, .appPurple, .navy, .oxblood: 54 | return .white 55 | } 56 | } 57 | 58 | var mainColor: Color { Color(rawValue) } 59 | 60 | var name: String { 61 | switch self { 62 | case .appIndigo: 63 | "indigo" 64 | case .appMagenta: 65 | "magent" 66 | case .appOrange: 67 | "orange" 68 | case .appPurple: 69 | "purple" 70 | case .appTeal: 71 | "teal" 72 | case .yellow: 73 | "yellow" 74 | case .bubblegum: 75 | "bubblegum" 76 | case .buttercup: 77 | "buttercup" 78 | case .lavender: 79 | "lavender" 80 | case .navy: 81 | "navy" 82 | case .oxblood: 83 | "oxblood" 84 | case .periwinkle: 85 | "periwinkle" 86 | case .poppy: 87 | "poppy" 88 | case .seafoam: 89 | "seafoam" 90 | case .sky: 91 | "sky" 92 | case .tan: 93 | "tan" 94 | } 95 | } 96 | } 97 | 98 | extension SyncUp { 99 | static let mock = Self( 100 | id: SyncUp.ID(), 101 | attendees: [ 102 | Attendee(id: Attendee.ID(), name: "Blob"), 103 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 104 | Attendee(id: Attendee.ID(), name: "Blob Sr"), 105 | Attendee(id: Attendee.ID(), name: "Blob Esq"), 106 | Attendee(id: Attendee.ID(), name: "Blob III"), 107 | Attendee(id: Attendee.ID(), name: "Blob I"), 108 | ], 109 | duration: .seconds(60), 110 | meetings: [ 111 | Meeting( 112 | id: Meeting.ID(), 113 | date: Date().addingTimeInterval(-60 * 60 * 24 * 7), 114 | transcript: """ 115 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \ 116 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \ 117 | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure \ 118 | dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \ 119 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \ 120 | mollit anim id est laborum. 121 | """ 122 | ) 123 | ], 124 | theme: .appOrange, 125 | title: "Design" 126 | ) 127 | 128 | static let engineeringMock = Self( 129 | id: SyncUp.ID(), 130 | attendees: [ 131 | Attendee(id: Attendee.ID(), name: "Blob"), 132 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 133 | ], 134 | duration: .seconds(60 * 10), 135 | theme: .periwinkle, 136 | title: "Engineering" 137 | ) 138 | 139 | static let productMock = Self( 140 | id: SyncUp.ID(), 141 | attendees: [ 142 | Attendee(id: Attendee.ID(), name: "Blob Sr"), 143 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 144 | ], 145 | duration: .seconds(60 * 30), 146 | theme: .poppy, 147 | title: "Product" 148 | ) 149 | } 150 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/RecordMeeting.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct RecordMeetingFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | let syncUp: SyncUp 9 | var elapsedSeconds = 0 10 | var speakerIndex = 0 11 | var durationRemaining: Duration { 12 | syncUp.duration - .seconds(elapsedSeconds) 13 | } 14 | } 15 | enum Action { 16 | case onAppear 17 | case timerTick 18 | } 19 | @Dependency(\.dismiss) var dismiss 20 | var body: some ReducerOf { 21 | Reduce { state, action in 22 | switch action { 23 | case .onAppear: 24 | return .run { send in 25 | for await _ in ContinuousClock().timer(interval: .seconds(1)) { 26 | await send(.timerTick) 27 | } 28 | } 29 | 30 | case .timerTick: 31 | state.elapsedSeconds += 1 32 | let secondsPerAttendee = Int(state.syncUp.durationPerAttendee.components.seconds) 33 | if state.elapsedSeconds.isMultiple(of: secondsPerAttendee) { 34 | state.speakerIndex += 1 35 | if state.speakerIndex >= state.syncUp.attendees.count { 36 | return .run { _ in 37 | await dismiss() 38 | } 39 | } 40 | } 41 | return .none 42 | } 43 | } 44 | } 45 | } 46 | 47 | struct RecordMeetingView: View { 48 | let store: StoreOf 49 | 50 | var body: some View { 51 | ZStack { 52 | RoundedRectangle(cornerRadius: 16) 53 | .fill(store.syncUp.theme.mainColor) 54 | 55 | VStack { 56 | MeetingHeaderView( 57 | secondsElapsed: store.elapsedSeconds, 58 | durationRemaining: store.durationRemaining, 59 | theme: store.syncUp.theme 60 | ) 61 | MeetingTimerView( 62 | syncUp: store.syncUp, 63 | speakerIndex: store.speakerIndex 64 | ) 65 | MeetingFooterView( 66 | syncUp: store.syncUp, 67 | nextButtonTapped: { 68 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 69 | }, 70 | speakerIndex: store.speakerIndex 71 | ) 72 | } 73 | } 74 | .padding() 75 | .foregroundColor(store.syncUp.theme.accentColor) 76 | .navigationBarTitleDisplayMode(.inline) 77 | .toolbar { 78 | ToolbarItem(placement: .cancellationAction) { 79 | Button("End meeting") { 80 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 81 | } 82 | } 83 | } 84 | .interactiveDismissDisabled(true) 85 | .navigationBarBackButtonHidden(true) 86 | .onAppear { store.send(.onAppear) } 87 | } 88 | } 89 | 90 | #Preview { 91 | NavigationStack { 92 | RecordMeetingView( 93 | store: Store( 94 | initialState: RecordMeetingFeature.State( 95 | syncUp: SyncUp( 96 | id: UUID(), 97 | attendees: [ 98 | Attendee(id: UUID(), name: "Blob 1"), 99 | Attendee(id: UUID(), name: "Blob 2"), 100 | Attendee(id: UUID(), name: "Blob 3"), 101 | Attendee(id: UUID(), name: "Blob 4"), 102 | Attendee(id: UUID(), name: "Blob 5"), 103 | Attendee(id: UUID(), name: "Blob 6"), 104 | ], 105 | duration: .seconds(12) 106 | ) 107 | ) 108 | ) { 109 | RecordMeetingFeature() 110 | ._printChanges() 111 | }) 112 | 113 | } 114 | } 115 | 116 | struct MeetingHeaderView: View { 117 | let secondsElapsed: Int 118 | let durationRemaining: Duration 119 | let theme: Theme 120 | 121 | var body: some View { 122 | VStack { 123 | ProgressView(value: progress) 124 | .progressViewStyle(MeetingProgressViewStyle(theme: theme)) 125 | HStack { 126 | VStack(alignment: .leading) { 127 | Text("Time Elapsed") 128 | .font(.caption) 129 | Label( 130 | Duration.seconds(secondsElapsed).formatted(.units()), 131 | systemImage: "hourglass.bottomhalf.fill" 132 | ) 133 | } 134 | Spacer() 135 | VStack(alignment: .trailing) { 136 | Text("Time Remaining") 137 | .font(.caption) 138 | Label(durationRemaining.formatted(.units()), systemImage: "hourglass.tophalf.fill") 139 | .font(.body.monospacedDigit()) 140 | .labelStyle(.trailingIcon) 141 | } 142 | } 143 | } 144 | .padding([.top, .horizontal]) 145 | } 146 | 147 | private var totalDuration: Duration { 148 | .seconds(secondsElapsed) + durationRemaining 149 | } 150 | 151 | private var progress: Double { 152 | guard totalDuration > .seconds(0) else { return 0 } 153 | return Double(secondsElapsed) / Double(totalDuration.components.seconds) 154 | } 155 | } 156 | 157 | struct MeetingProgressViewStyle: ProgressViewStyle { 158 | var theme: Theme 159 | 160 | func makeBody(configuration: Configuration) -> some View { 161 | ZStack { 162 | RoundedRectangle(cornerRadius: 10) 163 | .fill(theme.accentColor) 164 | .frame(height: 20) 165 | 166 | ProgressView(configuration) 167 | .tint(theme.mainColor) 168 | .frame(height: 12) 169 | .padding(.horizontal) 170 | } 171 | } 172 | } 173 | 174 | struct MeetingTimerView: View { 175 | let syncUp: SyncUp 176 | let speakerIndex: Int 177 | 178 | var body: some View { 179 | Circle() 180 | .strokeBorder(lineWidth: 24) 181 | .overlay { 182 | VStack { 183 | Group { 184 | if speakerIndex < syncUp.attendees.count { 185 | Text(syncUp.attendees[speakerIndex].name) 186 | } else { 187 | Text("Someone") 188 | } 189 | } 190 | .font(.title) 191 | Text("is speaking") 192 | Image(systemName: "mic.fill") 193 | .font(.largeTitle) 194 | .padding(.top) 195 | } 196 | .foregroundStyle(syncUp.theme.accentColor) 197 | } 198 | .overlay { 199 | ForEach(Array(syncUp.attendees.enumerated()), id: \.element.id) { index, attendee in 200 | if index < speakerIndex + 1 { 201 | SpeakerArc(totalSpeakers: syncUp.attendees.count, speakerIndex: index) 202 | .rotation(Angle(degrees: -90)) 203 | .stroke(syncUp.theme.mainColor, lineWidth: 12) 204 | } 205 | } 206 | } 207 | .padding(.horizontal) 208 | } 209 | } 210 | 211 | struct SpeakerArc: Shape { 212 | let totalSpeakers: Int 213 | let speakerIndex: Int 214 | 215 | func path(in rect: CGRect) -> Path { 216 | let diameter = min(rect.size.width, rect.size.height) - 24 217 | let radius = diameter / 2 218 | let center = CGPoint(x: rect.midX, y: rect.midY) 219 | return Path { path in 220 | path.addArc( 221 | center: center, 222 | radius: radius, 223 | startAngle: startAngle, 224 | endAngle: endAngle, 225 | clockwise: false 226 | ) 227 | } 228 | } 229 | 230 | private var degreesPerSpeaker: Double { 231 | 360 / Double(totalSpeakers) 232 | } 233 | private var startAngle: Angle { 234 | Angle(degrees: degreesPerSpeaker * Double(speakerIndex) + 1) 235 | } 236 | private var endAngle: Angle { 237 | Angle(degrees: startAngle.degrees + degreesPerSpeaker - 1) 238 | } 239 | } 240 | 241 | struct MeetingFooterView: View { 242 | let syncUp: SyncUp 243 | var nextButtonTapped: () -> Void 244 | let speakerIndex: Int 245 | 246 | var body: some View { 247 | VStack { 248 | HStack { 249 | if speakerIndex < syncUp.attendees.count - 1 { 250 | Text("Speaker \(speakerIndex + 1) of \(syncUp.attendees.count)") 251 | } else { 252 | Text("No more speakers.") 253 | } 254 | Spacer() 255 | Button(action: nextButtonTapped) { 256 | Image(systemName: "forward.fill") 257 | } 258 | } 259 | } 260 | .padding([.bottom, .horizontal]) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/SyncUpDetail.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct SyncUpDetailFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | @Presents var alert: AlertState? 9 | @Presents var editSyncUp: SyncUpFormFeature.State? 10 | @Presents var recordMeeting: RecordMeetingFeature.State? 11 | @Shared var syncUp: SyncUp 12 | } 13 | enum Action { 14 | case alert(PresentationAction) 15 | case editButtonTapped 16 | case editSyncUp(PresentationAction) 17 | case cancelButtonTapped 18 | case saveButtonTapped 19 | case deleteButtonTapped 20 | case recordMeeting(PresentationAction) 21 | case startMeetingButtonTapped 22 | 23 | enum Alert { 24 | case confirmDeletion 25 | } 26 | } 27 | 28 | //@Environment(\.dismiss) var dismiss 29 | @Dependency(\.dismiss) var dismiss 30 | 31 | var body: some ReducerOf { 32 | Reduce { state, action in 33 | switch action { 34 | case .alert(.presented(.confirmDeletion)): 35 | return .run { [id = state.syncUp.id] _ in 36 | await dismiss() 37 | @Shared(.fileStorage(.syncUps)) var syncUps: [SyncUp] = [] 38 | syncUps.removeAll(where: { $0.id == id }) 39 | } 40 | 41 | case .alert: 42 | return .none 43 | 44 | case .editButtonTapped: 45 | state.editSyncUp = SyncUpFormFeature.State(syncUp: state.syncUp) 46 | return .none 47 | case .editSyncUp: 48 | return .none 49 | case .cancelButtonTapped: 50 | state.editSyncUp = nil 51 | return .none 52 | case .saveButtonTapped: 53 | guard let syncUp = state.editSyncUp?.syncUp 54 | else { return .none } 55 | state.syncUp = syncUp 56 | state.editSyncUp = nil 57 | return .none 58 | case .deleteButtonTapped: 59 | state.alert = AlertState { 60 | TextState("Are you sure you want to delete this sync up?") 61 | } actions: { 62 | ButtonState(role: .destructive, action: .confirmDeletion) { 63 | TextState("Delete") 64 | } 65 | } 66 | return .none 67 | 68 | case .recordMeeting: 69 | return .none 70 | 71 | case .startMeetingButtonTapped: 72 | state.recordMeeting = RecordMeetingFeature.State(syncUp: state.syncUp) 73 | return .none 74 | } 75 | } 76 | .ifLet(\.$editSyncUp, action: \.editSyncUp) { 77 | SyncUpFormFeature() 78 | } 79 | .ifLet(\.$alert, action: \.alert) 80 | .ifLet(\.$recordMeeting, action: \.recordMeeting) { 81 | RecordMeetingFeature() 82 | } 83 | } 84 | } 85 | 86 | struct SyncUpDetailView: View { 87 | @Bindable var store: StoreOf 88 | 89 | var body: some View { 90 | Form { 91 | Section { 92 | Button { 93 | store.send(.startMeetingButtonTapped) 94 | } label: { 95 | Label("Start Meeting", systemImage: "timer") 96 | .font(.headline) 97 | .foregroundColor(.accentColor) 98 | } 99 | HStack { 100 | Label("Length", systemImage: "clock") 101 | Spacer() 102 | Text(store.syncUp.duration.formatted(.units())) 103 | } 104 | 105 | HStack { 106 | Label("Theme", systemImage: "paintpalette") 107 | Spacer() 108 | Text(store.syncUp.title) 109 | .padding(4) 110 | .foregroundColor(store.syncUp.theme.accentColor) 111 | .background(store.syncUp.theme.mainColor) 112 | .cornerRadius(4) 113 | } 114 | } header: { 115 | Text("Sync-up Info") 116 | } 117 | 118 | if !store.syncUp.meetings.isEmpty { 119 | Section { 120 | ForEach(store.syncUp.meetings) { meeting in 121 | NavigationLink { 122 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 123 | } label: { 124 | HStack { 125 | Image(systemName: "calendar") 126 | Text(meeting.date, style: .date) 127 | Text(meeting.date, style: .time) 128 | } 129 | } 130 | } 131 | .onDelete { indices in 132 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 133 | } 134 | } header: { 135 | Text("Past meetings") 136 | } 137 | } 138 | 139 | Section { 140 | ForEach(store.syncUp.attendees) { attendee in 141 | Label(attendee.name, systemImage: "person") 142 | } 143 | } header: { 144 | Text("Attendees") 145 | } 146 | 147 | Section { 148 | Button("Delete") { 149 | store.send(.deleteButtonTapped) 150 | } 151 | .foregroundColor(.red) 152 | .frame(maxWidth: .infinity) 153 | } 154 | } 155 | .toolbar { 156 | Button("Edit") { 157 | store.send(.editButtonTapped) 158 | } 159 | } 160 | .navigationTitle(store.syncUp.title) 161 | .sheet(item: $store.scope(state: \.editSyncUp, action: \.editSyncUp)) { editSyncUpStore in 162 | NavigationStack { 163 | SyncUpFormView(store: editSyncUpStore) 164 | .navigationTitle(Text("Edit sync-up")) 165 | .toolbar { 166 | ToolbarItem(placement: .cancellationAction) { 167 | Button("Cancel") { 168 | store.send(.cancelButtonTapped) 169 | } 170 | } 171 | ToolbarItem { 172 | Button("Save") { 173 | store.send(.saveButtonTapped) 174 | } 175 | } 176 | } 177 | } 178 | } 179 | .alert($store.scope(state: \.alert, action: \.alert)) 180 | .sheet(item: $store.scope(state: \.recordMeeting, action: \.recordMeeting)) { recordMeetingStore in 181 | NavigationStack { 182 | RecordMeetingView(store: recordMeetingStore) 183 | } 184 | } 185 | } 186 | } 187 | 188 | #Preview { 189 | NavigationStack { 190 | SyncUpDetailView( 191 | store: Store(initialState: SyncUpDetailFeature.State(syncUp: Shared(.mock))) { 192 | SyncUpDetailFeature() 193 | ._printChanges() 194 | } 195 | ) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/SyncUpForm.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct SyncUpFormFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | var syncUp: SyncUp 9 | } 10 | enum Action: BindableAction { 11 | case binding(BindingAction) 12 | case onDeleteAttendees(IndexSet) 13 | case addAttendeeButtonTapped 14 | } 15 | var body: some ReducerOf { 16 | BindingReducer() 17 | Reduce { state, action in 18 | switch action { 19 | case .binding(_): 20 | return .none 21 | case let .onDeleteAttendees(indexSet): 22 | state.syncUp.attendees.remove(atOffsets: indexSet) 23 | return .none 24 | case .addAttendeeButtonTapped: 25 | state.syncUp.attendees.append(Attendee(id: UUID())) 26 | return .none 27 | } 28 | } 29 | } 30 | } 31 | 32 | struct SyncUpFormView: View { 33 | @Bindable var store: StoreOf 34 | 35 | var body: some View { 36 | Form { 37 | Section { 38 | TextField("Title", text: $store.syncUp.title) 39 | HStack { 40 | Slider(value: $store.syncUp.duration.minutes, in: 5...30, step: 1) { 41 | Text("Length") 42 | } 43 | Spacer() 44 | Text(store.syncUp.duration.formatted(.units())) 45 | } 46 | ThemePicker(selection: $store.syncUp.theme) 47 | } header: { 48 | Text("Sync-up Info") 49 | } 50 | Section { 51 | ForEach($store.syncUp.attendees) { $attendee in 52 | TextField("Name", text: $attendee.name) 53 | } 54 | .onDelete { indices in 55 | store.send(.onDeleteAttendees(indices)) 56 | } 57 | 58 | Button("New attendee") { 59 | store.send(.addAttendeeButtonTapped) 60 | } 61 | } header: { 62 | Text("Attendees") 63 | } 64 | } 65 | } 66 | } 67 | 68 | #Preview { 69 | NavigationStack { 70 | SyncUpFormView( 71 | store: Store(initialState: SyncUpFormFeature.State(syncUp: .mock)) { 72 | SyncUpFormFeature() 73 | ._printChanges() 74 | } 75 | ) 76 | } 77 | } 78 | 79 | struct ThemePicker: View { 80 | @Binding var selection: Theme 81 | 82 | var body: some View { 83 | Picker("Theme", selection: $selection) { 84 | ForEach(Theme.allCases) { theme in 85 | ZStack { 86 | RoundedRectangle(cornerRadius: 4) 87 | .fill(theme.mainColor) 88 | Label(theme.name, systemImage: "paintpalette") 89 | .padding(4) 90 | } 91 | .foregroundColor(theme.accentColor) 92 | .fixedSize(horizontal: false, vertical: true) 93 | .tag(theme) 94 | } 95 | } 96 | } 97 | } 98 | 99 | extension Duration { 100 | fileprivate var minutes: Double { 101 | get { Double(components.seconds / 60) } 102 | set { self = .seconds(newValue * 60) } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /in-progress/TrySyncUps/SyncUpsList.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | extension URL { 5 | static let syncUps = URL.documentsDirectory.appending(path: "sync-ups.json") 6 | } 7 | 8 | @Reducer 9 | struct SyncUpsListFeature { 10 | @Reducer(state: .equatable) 11 | enum Destination { 12 | case addSyncUp(SyncUpFormFeature) 13 | case syncUpDetail(SyncUpDetailFeature) 14 | } 15 | 16 | @ObservableState 17 | struct State: Equatable { 18 | // @Presents var addSyncUp: SyncUpFormFeature.State? 19 | // @Presents var syncUpDetail: SyncUpDetailFeature.State? 20 | @Presents var destination: Destination.State? 21 | @Shared(.fileStorage(.syncUps)) var syncUps: [SyncUp] = [] 22 | } 23 | enum Action { 24 | case addButtonTapped 25 | // case addSyncUp(PresentationAction) 26 | // case syncUpDetail(PresentationAction) 27 | case destination(PresentationAction) 28 | case cancelButtonTapped 29 | case onDelete(_ indexSet: IndexSet) 30 | case syncUpTapped(id: SyncUp.ID) 31 | case addSyncUpButtonTapped 32 | } 33 | @Dependency(\.uuid) var uuid 34 | var body: some ReducerOf { 35 | Reduce { state, action in 36 | switch action { 37 | case .addButtonTapped: 38 | guard let syncUp = state.destination?.addSyncUp?.syncUp 39 | else { return .none } 40 | state.syncUps.append(syncUp) 41 | state.destination = nil 42 | return .none 43 | case .cancelButtonTapped: 44 | state.destination = nil 45 | return .none 46 | case let .onDelete(indexSet): 47 | state.syncUps.remove(atOffsets: indexSet) 48 | return .none 49 | case let .syncUpTapped(id): 50 | guard let syncUpIndex = state.syncUps.firstIndex(where: { $0.id == id }) 51 | else { return .none } 52 | state.destination = .syncUpDetail(SyncUpDetailFeature.State(syncUp: state.$syncUps[syncUpIndex])) 53 | return .none 54 | case .addSyncUpButtonTapped: 55 | state.destination = .addSyncUp(SyncUpFormFeature.State(syncUp: SyncUp(id: uuid()))) 56 | return .none 57 | case .destination: 58 | return .none 59 | } 60 | } 61 | // .ifLet(\.$addSyncUp, action: \.addSyncUp) { 62 | // SyncUpFormFeature() 63 | // } 64 | // .ifLet(\.$syncUpDetail, action: \.syncUpDetail) { 65 | // SyncUpDetailFeature() 66 | // } 67 | .ifLet(\.$destination, action: \.destination) { 68 | Destination.body 69 | } 70 | } 71 | } 72 | 73 | @Observable 74 | class SyncUpsListModel { 75 | var syncUps: [SyncUp] = [] 76 | 77 | init(syncUps: [SyncUp] = []) { 78 | self.syncUps = syncUps 79 | } 80 | 81 | func onDelete(_ indexSet: IndexSet) { 82 | self.syncUps.remove(atOffsets: indexSet) 83 | } 84 | 85 | func syncUpTapped(id: SyncUp.ID) { 86 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 87 | } 88 | 89 | func addSyncUpButtonTapped() { 90 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 91 | } 92 | } 93 | 94 | struct SyncUpsListView: View { 95 | //let model: SyncUpsListModel 96 | @Bindable var store: StoreOf 97 | 98 | var body: some View { 99 | List { 100 | ForEach(store.syncUps) { syncUp in 101 | Button { 102 | //model.syncUpTapped(id: syncUp.id) 103 | store.send(.syncUpTapped(id: syncUp.id)) 104 | } label: { 105 | CardView(syncUp: syncUp) 106 | } 107 | .listRowBackground(syncUp.theme.mainColor) 108 | } 109 | .onDelete { indexSet in 110 | //model.onDelete(indexSet) 111 | store.send(.onDelete(indexSet)) 112 | } 113 | } 114 | .toolbar { 115 | Button { 116 | //model.addSyncUpButtonTapped() 117 | store.send(.addSyncUpButtonTapped) 118 | } label: { 119 | Image(systemName: "plus") 120 | } 121 | } 122 | .navigationTitle("Daily Sync-ups") 123 | .sheet(item: $store.scope(state: \.destination?.addSyncUp, action: \.destination.addSyncUp)) { addSyncUpStore in 124 | NavigationStack { 125 | SyncUpFormView(store: addSyncUpStore) 126 | .navigationTitle("New sync-up") 127 | .toolbar { 128 | ToolbarItem { 129 | Button("Add") { 130 | store.send(.addButtonTapped) 131 | } 132 | } 133 | ToolbarItem(placement: .cancellationAction) { 134 | Button("Cancel") { 135 | store.send(.cancelButtonTapped) 136 | } 137 | } 138 | } 139 | } 140 | } 141 | .navigationDestination(item: $store.scope(state: \.destination?.syncUpDetail, action: \.destination.syncUpDetail)) { syncUpDetailStore in 142 | SyncUpDetailView(store: syncUpDetailStore) 143 | } 144 | } 145 | } 146 | 147 | #Preview { 148 | NavigationStack { 149 | SyncUpsListView( 150 | store: Store( 151 | initialState: SyncUpsListFeature.State( 152 | // addSyncUp: SyncUpFormFeature.State(syncUp: .mock), 153 | syncUps: [ 154 | SyncUp( 155 | id: UUID(), 156 | attendees: [ 157 | Attendee(id: UUID(), name: "Blob 1"), 158 | Attendee(id: UUID(), name: "Blob 2"), 159 | Attendee(id: UUID(), name: "Blob 3"), 160 | Attendee(id: UUID(), name: "Blob 4"), 161 | Attendee(id: UUID(), name: "Blob 5"), 162 | Attendee(id: UUID(), name: "Blob 6"), 163 | ], 164 | duration: .seconds(6) 165 | ) 166 | // .mock, .engineeringMock, .productMock 167 | ] 168 | ) 169 | ) { 170 | SyncUpsListFeature()._printChanges() 171 | } 172 | ) 173 | } 174 | } 175 | 176 | struct CardView: View { 177 | let syncUp: SyncUp 178 | 179 | var body: some View { 180 | VStack(alignment: .leading) { 181 | Text(syncUp.title) 182 | .font(.headline) 183 | Spacer() 184 | HStack { 185 | Label("\(syncUp.attendees.count)", systemImage: "person.3") 186 | Spacer() 187 | Label(syncUp.duration.formatted(.units()), systemImage: "clock") 188 | .labelStyle(.trailingIcon) 189 | } 190 | .font(.caption) 191 | } 192 | .padding() 193 | .foregroundColor(syncUp.theme.accentColor) 194 | } 195 | } 196 | 197 | struct TrailingIconLabelStyle: LabelStyle { 198 | func makeBody(configuration: Configuration) -> some View { 199 | HStack { 200 | configuration.title 201 | configuration.icon 202 | } 203 | } 204 | } 205 | 206 | extension LabelStyle where Self == TrailingIconLabelStyle { 207 | static var trailingIcon: Self { Self() } 208 | } 209 | -------------------------------------------------------------------------------- /in-progress/TrySyncUpsTests/RecordMeetingsTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class RecordMeetingTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /in-progress/TrySyncUpsTests/SyncUpDetailTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpDetailTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /in-progress/TrySyncUpsTests/SyncUpFormTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpFormTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /in-progress/TrySyncUpsTests/SyncUpsListTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpsListTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | let store = TestStore(initialState: SyncUpsListFeature.State(syncUps: [.mock, .productMock])) { 10 | SyncUpsListFeature() 11 | } 12 | await store.send(.onDelete([1])) { 13 | $0.syncUps.remove(at: 1) 14 | } 15 | } 16 | 17 | @MainActor 18 | func testAddSyncUp() async { 19 | let store = TestStore(initialState: SyncUpsListFeature.State()) { 20 | SyncUpsListFeature() 21 | } withDependencies: { 22 | $0.uuid = .incrementing 23 | } 24 | 25 | await store.send(.addSyncUpButtonTapped) { 26 | $0.destination = .addSyncUp(SyncUpFormFeature.State(syncUp: SyncUp(id: UUID(0)))) 27 | } 28 | // await store.send(.addSyncUp(.presented(.set(\.syncUp, SyncUp(id: UUID(0), title: "Morning Sync"))))) 29 | await store.send(\.destination.addSyncUp.binding.syncUp, SyncUp(id: UUID(0), title: "Morning Sync")) { 30 | $0.destination?.addSyncUp?.syncUp.title = "Morning Sync" 31 | } 32 | await store.send(.addButtonTapped) { 33 | $0.destination = nil 34 | $0.syncUps = [ 35 | SyncUp(id: UUID(0), title: "Morning Sync") 36 | ] 37 | } 38 | } 39 | 40 | 41 | 42 | @MainActor 43 | func testModel() { 44 | let model = SyncUpsListModel(syncUps: [.mock, .productMock]) 45 | model.onDelete([1]) 46 | // XCTAssertEqual(model.syncUps, [.mock]) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /in-progress/TrySyncUpsTests/TrySyncUps.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "D568F12C-3DAF-4491-BA1B-B14568030DB9", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "targetForVariableExpansion" : { 13 | "containerPath" : "container:TrySyncUps.xcodeproj", 14 | "identifier" : "DC531C432BA3CF1E005C7270", 15 | "name" : "TrySyncUps" 16 | } 17 | }, 18 | "testTargets" : [ 19 | { 20 | "target" : { 21 | "containerPath" : "container:TrySyncUps.xcodeproj", 22 | "identifier" : "DC531C532BA3CF20005C7270", 23 | "name" : "TrySyncUpsTests" 24 | } 25 | } 26 | ], 27 | "version" : 1 28 | } 29 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "81d703dd6e7ef361d922e4159d56e99fa7337762368097761b4ba0ff022b4d31", 3 | "pins" : [ 4 | { 5 | "identity" : "combine-schedulers", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/pointfreeco/combine-schedulers", 8 | "state" : { 9 | "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", 10 | "version" : "1.0.0" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-case-paths", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/pointfreeco/swift-case-paths", 17 | "state" : { 18 | "revision" : "e593aba2c6222daad7c4f2732a431eed2c09bb07", 19 | "version" : "1.3.0" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-clocks", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/pointfreeco/swift-clocks", 26 | "state" : { 27 | "revision" : "a8421d68068d8f45fbceb418fbf22c5dad4afd33", 28 | "version" : "1.0.2" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-collections", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/apple/swift-collections", 35 | "state" : { 36 | "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", 37 | "version" : "1.1.0" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-composable-architecture", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", 44 | "state" : { 45 | "branch" : "shared-state-beta", 46 | "revision" : "ad00d06a272f31cd2a409f7f683f4af4c71d90c4" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-concurrency-extras", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/pointfreeco/swift-concurrency-extras", 53 | "state" : { 54 | "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", 55 | "version" : "1.1.0" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-custom-dump", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 62 | "state" : { 63 | "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", 64 | "version" : "1.3.0" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-dependencies", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/pointfreeco/swift-dependencies", 71 | "state" : { 72 | "revision" : "d3a5af3038a09add4d7682f66555d6212058a3c0", 73 | "version" : "1.2.2" 74 | } 75 | }, 76 | { 77 | "identity" : "swift-identified-collections", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/pointfreeco/swift-identified-collections", 80 | "state" : { 81 | "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", 82 | "version" : "1.0.0" 83 | } 84 | }, 85 | { 86 | "identity" : "swift-perception", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/pointfreeco/swift-perception", 89 | "state" : { 90 | "revision" : "a5bb578d963fcdbffe4fd56c92b2e222f5b02c8a", 91 | "version" : "1.1.2" 92 | } 93 | }, 94 | { 95 | "identity" : "swift-syntax", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/apple/swift-syntax", 98 | "state" : { 99 | "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", 100 | "version" : "510.0.1" 101 | } 102 | }, 103 | { 104 | "identity" : "swiftui-navigation", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/pointfreeco/swiftui-navigation", 107 | "state" : { 108 | "revision" : "d9e72f3083c08375794afa216fb2f89c0114f303", 109 | "version" : "1.2.1" 110 | } 111 | }, 112 | { 113 | "identity" : "xctest-dynamic-overlay", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 116 | "state" : { 117 | "revision" : "b13b1d1a8e787a5ffc71ac19dcaf52183ab27ba2", 118 | "version" : "1.1.1" 119 | } 120 | } 121 | ], 122 | "version" : 3 123 | } 124 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps.xcodeproj/xcshareddata/xcschemes/TrySyncUps.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 35 | 36 | 37 | 38 | 40 | 46 | 47 | 48 | 49 | 50 | 60 | 62 | 68 | 69 | 70 | 71 | 77 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/App.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @main 5 | struct SyncUpsApp: App { 6 | var body: some Scene { 7 | WindowGroup { 8 | NavigationStack { 9 | SyncUpFormView() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/appIndigo.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.443", 9 | "green" : "0.000", 10 | "red" : "0.212" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.443", 27 | "green" : "0.000", 28 | "red" : "0.212" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/appMagenta.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.467", 9 | "green" : "0.075", 10 | "red" : "0.647" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.467", 27 | "green" : "0.075", 28 | "red" : "0.647" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/appOrange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.259", 9 | "green" : "0.545", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.259", 27 | "green" : "0.545", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/appPurple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.949", 9 | "green" : "0.294", 10 | "red" : "0.569" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.949", 27 | "green" : "0.294", 28 | "red" : "0.569" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/appTeal.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.620", 9 | "green" : "0.561", 10 | "red" : "0.133" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.620", 27 | "green" : "0.561", 28 | "red" : "0.133" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/bubblegum.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.820", 9 | "green" : "0.502", 10 | "red" : "0.933" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.820", 27 | "green" : "0.502", 28 | "red" : "0.933" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/buttercup.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.588", 9 | "green" : "0.945", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.588", 27 | "green" : "0.945", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/lavender.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.808", 10 | "red" : "0.812" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.808", 28 | "red" : "0.812" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/navy.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.255", 9 | "green" : "0.078", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.255", 27 | "green" : "0.078", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/oxblood.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.043", 9 | "green" : "0.027", 10 | "red" : "0.290" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.043", 27 | "green" : "0.027", 28 | "red" : "0.290" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/periwinkle.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.510", 10 | "red" : "0.525" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.510", 28 | "red" : "0.525" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/poppy.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.369", 9 | "green" : "0.369", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.369", 27 | "green" : "0.369", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/seafoam.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.898", 9 | "green" : "0.918", 10 | "red" : "0.796" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.898", 27 | "green" : "0.918", 28 | "red" : "0.796" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/sky.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.573", 10 | "red" : "0.431" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.573", 28 | "red" : "0.431" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/tan.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.494", 9 | "green" : "0.608", 10 | "red" : "0.761" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.494", 27 | "green" : "0.608", 28 | "red" : "0.761" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Assets.xcassets/Themes/yellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.302", 9 | "green" : "0.875", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.302", 27 | "green" : "0.875", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Meeting.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MeetingView: View { 4 | let meeting: Meeting 5 | let syncUp: SyncUp 6 | 7 | var body: some View { 8 | Form { 9 | Section { 10 | ForEach(syncUp.attendees) { attendee in 11 | Text(attendee.name) 12 | } 13 | } header: { 14 | Text("Attendees") 15 | } 16 | Section { 17 | Text(meeting.transcript) 18 | } header: { 19 | Text("Transcript") 20 | } 21 | } 22 | .navigationTitle(Text(meeting.date, style: .date)) 23 | } 24 | } 25 | 26 | #Preview { 27 | NavigationStack { 28 | MeetingView(meeting: SyncUp.mock.meetings[0], syncUp: .mock) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Models.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | struct SyncUp: Equatable, Identifiable, Codable { 5 | let id: UUID 6 | var attendees: IdentifiedArrayOf = [] 7 | var duration: Duration = .seconds(60 * 5) 8 | var meetings: IdentifiedArrayOf = [] 9 | var theme: Theme = .bubblegum 10 | var title = "" 11 | 12 | var durationPerAttendee: Duration { 13 | duration / attendees.count 14 | } 15 | } 16 | 17 | struct Attendee: Equatable, Identifiable, Codable { 18 | let id: UUID 19 | var name = "" 20 | } 21 | 22 | struct Meeting: Equatable, Identifiable, Codable { 23 | let id: UUID 24 | let date: Date 25 | var transcript: String 26 | } 27 | 28 | enum Theme: String, CaseIterable, Equatable, Identifiable, Codable { 29 | case appIndigo 30 | case appMagenta 31 | case appOrange 32 | case appPurple 33 | case appTeal 34 | case yellow 35 | case bubblegum 36 | case buttercup 37 | case lavender 38 | case navy 39 | case oxblood 40 | case periwinkle 41 | case poppy 42 | case seafoam 43 | case sky 44 | case tan 45 | 46 | var id: Self { self } 47 | 48 | var accentColor: Color { 49 | switch self { 50 | case .appOrange, .appTeal, .yellow, .bubblegum, .buttercup, .lavender, .periwinkle, .poppy, 51 | .seafoam, .sky, .tan: 52 | return .black 53 | case .appIndigo, .appMagenta, .appPurple, .navy, .oxblood: 54 | return .white 55 | } 56 | } 57 | 58 | var mainColor: Color { Color(rawValue) } 59 | 60 | var name: String { 61 | switch self { 62 | case .appIndigo: 63 | "indigo" 64 | case .appMagenta: 65 | "magent" 66 | case .appOrange: 67 | "orange" 68 | case .appPurple: 69 | "purple" 70 | case .appTeal: 71 | "teal" 72 | case .yellow: 73 | "yellow" 74 | case .bubblegum: 75 | "bubblegum" 76 | case .buttercup: 77 | "buttercup" 78 | case .lavender: 79 | "lavender" 80 | case .navy: 81 | "navy" 82 | case .oxblood: 83 | "oxblood" 84 | case .periwinkle: 85 | "periwinkle" 86 | case .poppy: 87 | "poppy" 88 | case .seafoam: 89 | "seafoam" 90 | case .sky: 91 | "sky" 92 | case .tan: 93 | "tan" 94 | } 95 | } 96 | } 97 | 98 | extension SyncUp { 99 | static let mock = Self( 100 | id: SyncUp.ID(), 101 | attendees: [ 102 | Attendee(id: Attendee.ID(), name: "Blob"), 103 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 104 | Attendee(id: Attendee.ID(), name: "Blob Sr"), 105 | Attendee(id: Attendee.ID(), name: "Blob Esq"), 106 | Attendee(id: Attendee.ID(), name: "Blob III"), 107 | Attendee(id: Attendee.ID(), name: "Blob I"), 108 | ], 109 | duration: .seconds(60), 110 | meetings: [ 111 | Meeting( 112 | id: Meeting.ID(), 113 | date: Date().addingTimeInterval(-60 * 60 * 24 * 7), 114 | transcript: """ 115 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \ 116 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \ 117 | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure \ 118 | dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \ 119 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \ 120 | mollit anim id est laborum. 121 | """ 122 | ) 123 | ], 124 | theme: .appOrange, 125 | title: "Design" 126 | ) 127 | 128 | static let engineeringMock = Self( 129 | id: SyncUp.ID(), 130 | attendees: [ 131 | Attendee(id: Attendee.ID(), name: "Blob"), 132 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 133 | ], 134 | duration: .seconds(60 * 10), 135 | theme: .periwinkle, 136 | title: "Engineering" 137 | ) 138 | 139 | static let productMock = Self( 140 | id: SyncUp.ID(), 141 | attendees: [ 142 | Attendee(id: Attendee.ID(), name: "Blob Sr"), 143 | Attendee(id: Attendee.ID(), name: "Blob Jr"), 144 | ], 145 | duration: .seconds(60 * 30), 146 | theme: .poppy, 147 | title: "Product" 148 | ) 149 | } 150 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/RecordMeeting.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | struct RecordMeetingView: View { 5 | var body: some View { 6 | ZStack { 7 | RoundedRectangle(cornerRadius: 16) 8 | .fill(/*@START_MENU_TOKEN@*//*@PLACEHOLDER=appOrange@*/Theme.appOrange/*@END_MENU_TOKEN@*/.mainColor) 9 | 10 | VStack { 11 | MeetingHeaderView( 12 | secondsElapsed: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=60 seconds@*/60/*@END_MENU_TOKEN@*/, 13 | durationRemaining: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=14 minutes@*/.seconds(60 * 14)/*@END_MENU_TOKEN@*/, 14 | theme: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=appOrange@*/Theme.appOrange/*@END_MENU_TOKEN@*/ 15 | ) 16 | MeetingTimerView( 17 | syncUp: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=syncUp@*/SyncUp.mock/*@END_MENU_TOKEN@*/, 18 | speakerIndex: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=0@*/0/*@END_MENU_TOKEN@*/ 19 | ) 20 | MeetingFooterView( 21 | syncUp: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=syncUp@*/SyncUp.mock/*@END_MENU_TOKEN@*/, 22 | nextButtonTapped: { 23 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 24 | }, 25 | speakerIndex: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=0@*/0/*@END_MENU_TOKEN@*/ 26 | ) 27 | } 28 | } 29 | .padding() 30 | .foregroundColor(/*@START_MENU_TOKEN@*//*@PLACEHOLDER=appOrange@*/Theme.appOrange/*@END_MENU_TOKEN@*/.accentColor) 31 | .navigationBarTitleDisplayMode(.inline) 32 | .toolbar { 33 | ToolbarItem(placement: .cancellationAction) { 34 | Button("End meeting") { 35 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 36 | } 37 | } 38 | } 39 | .interactiveDismissDisabled(true) 40 | .navigationBarBackButtonHidden(true) 41 | } 42 | } 43 | 44 | #Preview { 45 | NavigationStack { 46 | RecordMeetingView() 47 | } 48 | } 49 | 50 | struct MeetingHeaderView: View { 51 | let secondsElapsed: Int 52 | let durationRemaining: Duration 53 | let theme: Theme 54 | 55 | var body: some View { 56 | VStack { 57 | ProgressView(value: progress) 58 | .progressViewStyle(MeetingProgressViewStyle(theme: theme)) 59 | HStack { 60 | VStack(alignment: .leading) { 61 | Text("Time Elapsed") 62 | .font(.caption) 63 | Label( 64 | Duration.seconds(secondsElapsed).formatted(.units()), 65 | systemImage: "hourglass.bottomhalf.fill" 66 | ) 67 | } 68 | Spacer() 69 | VStack(alignment: .trailing) { 70 | Text("Time Remaining") 71 | .font(.caption) 72 | Label(durationRemaining.formatted(.units()), systemImage: "hourglass.tophalf.fill") 73 | .font(.body.monospacedDigit()) 74 | .labelStyle(.trailingIcon) 75 | } 76 | } 77 | } 78 | .padding([.top, .horizontal]) 79 | } 80 | 81 | private var totalDuration: Duration { 82 | .seconds(secondsElapsed) + durationRemaining 83 | } 84 | 85 | private var progress: Double { 86 | guard totalDuration > .seconds(0) else { return 0 } 87 | return Double(secondsElapsed) / Double(totalDuration.components.seconds) 88 | } 89 | } 90 | 91 | struct MeetingProgressViewStyle: ProgressViewStyle { 92 | var theme: Theme 93 | 94 | func makeBody(configuration: Configuration) -> some View { 95 | ZStack { 96 | RoundedRectangle(cornerRadius: 10) 97 | .fill(theme.accentColor) 98 | .frame(height: 20) 99 | 100 | ProgressView(configuration) 101 | .tint(theme.mainColor) 102 | .frame(height: 12) 103 | .padding(.horizontal) 104 | } 105 | } 106 | } 107 | 108 | struct MeetingTimerView: View { 109 | let syncUp: SyncUp 110 | let speakerIndex: Int 111 | 112 | var body: some View { 113 | Circle() 114 | .strokeBorder(lineWidth: 24) 115 | .overlay { 116 | VStack { 117 | Group { 118 | if speakerIndex < syncUp.attendees.count { 119 | Text(syncUp.attendees[speakerIndex].name) 120 | } else { 121 | Text("Someone") 122 | } 123 | } 124 | .font(.title) 125 | Text("is speaking") 126 | Image(systemName: "mic.fill") 127 | .font(.largeTitle) 128 | .padding(.top) 129 | } 130 | .foregroundStyle(syncUp.theme.accentColor) 131 | } 132 | .overlay { 133 | ForEach(Array(syncUp.attendees.enumerated()), id: \.element.id) { index, attendee in 134 | if index < speakerIndex + 1 { 135 | SpeakerArc(totalSpeakers: syncUp.attendees.count, speakerIndex: index) 136 | .rotation(Angle(degrees: -90)) 137 | .stroke(syncUp.theme.mainColor, lineWidth: 12) 138 | } 139 | } 140 | } 141 | .padding(.horizontal) 142 | } 143 | } 144 | 145 | struct SpeakerArc: Shape { 146 | let totalSpeakers: Int 147 | let speakerIndex: Int 148 | 149 | func path(in rect: CGRect) -> Path { 150 | let diameter = min(rect.size.width, rect.size.height) - 24 151 | let radius = diameter / 2 152 | let center = CGPoint(x: rect.midX, y: rect.midY) 153 | return Path { path in 154 | path.addArc( 155 | center: center, 156 | radius: radius, 157 | startAngle: startAngle, 158 | endAngle: endAngle, 159 | clockwise: false 160 | ) 161 | } 162 | } 163 | 164 | private var degreesPerSpeaker: Double { 165 | 360 / Double(totalSpeakers) 166 | } 167 | private var startAngle: Angle { 168 | Angle(degrees: degreesPerSpeaker * Double(speakerIndex) + 1) 169 | } 170 | private var endAngle: Angle { 171 | Angle(degrees: startAngle.degrees + degreesPerSpeaker - 1) 172 | } 173 | } 174 | 175 | struct MeetingFooterView: View { 176 | let syncUp: SyncUp 177 | var nextButtonTapped: () -> Void 178 | let speakerIndex: Int 179 | 180 | var body: some View { 181 | VStack { 182 | HStack { 183 | if speakerIndex < syncUp.attendees.count - 1 { 184 | Text("Speaker \(speakerIndex + 1) of \(syncUp.attendees.count)") 185 | } else { 186 | Text("No more speakers.") 187 | } 188 | Spacer() 189 | Button(action: nextButtonTapped) { 190 | Image(systemName: "forward.fill") 191 | } 192 | } 193 | } 194 | .padding([.bottom, .horizontal]) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/SyncUpDetail.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct SyncUpDetailFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | var syncUp: SyncUp 9 | } 10 | enum Action { 11 | } 12 | var body: some ReducerOf { 13 | EmptyReducer() 14 | } 15 | } 16 | 17 | struct SyncUpDetailView: View { 18 | @Bindable var store: StoreOf 19 | 20 | var body: some View { 21 | Form { 22 | Section { 23 | Button { 24 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 25 | } label: { 26 | Label("Start Meeting", systemImage: "timer") 27 | .font(.headline) 28 | .foregroundColor(.accentColor) 29 | } 30 | HStack { 31 | Label("Length", systemImage: "clock") 32 | Spacer() 33 | Text(store.syncUp.duration.formatted(.units())) 34 | } 35 | 36 | HStack { 37 | Label("Theme", systemImage: "paintpalette") 38 | Spacer() 39 | Text(store.syncUp.title) 40 | .padding(4) 41 | .foregroundColor(store.syncUp.theme.accentColor) 42 | .background(store.syncUp.theme.mainColor) 43 | .cornerRadius(4) 44 | } 45 | } header: { 46 | Text("Sync-up Info") 47 | } 48 | 49 | if !store.syncUp.meetings.isEmpty { 50 | Section { 51 | ForEach(store.syncUp.meetings) { meeting in 52 | NavigationLink { 53 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 54 | } label: { 55 | HStack { 56 | Image(systemName: "calendar") 57 | Text(meeting.date, style: .date) 58 | Text(meeting.date, style: .time) 59 | } 60 | } 61 | } 62 | .onDelete { indices in 63 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 64 | } 65 | } header: { 66 | Text("Past meetings") 67 | } 68 | } 69 | 70 | Section { 71 | ForEach(store.syncUp.attendees) { attendee in 72 | Label(attendee.name, systemImage: "person") 73 | } 74 | } header: { 75 | Text("Attendees") 76 | } 77 | 78 | Section { 79 | Button("Delete") { 80 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 81 | } 82 | .foregroundColor(.red) 83 | .frame(maxWidth: .infinity) 84 | } 85 | } 86 | .toolbar { 87 | Button("Edit") { 88 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 89 | } 90 | } 91 | .navigationTitle(store.syncUp.title) 92 | } 93 | } 94 | 95 | #Preview { 96 | NavigationStack { 97 | SyncUpDetailView( 98 | store: Store(initialState: SyncUpDetailFeature.State(syncUp: .mock)) { 99 | SyncUpDetailFeature() 100 | } 101 | ) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/SyncUpForm.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | @Reducer 5 | struct SyncUpFormFeature { 6 | @ObservableState 7 | struct State: Equatable { 8 | var syncUp: SyncUp 9 | } 10 | enum Action { 11 | } 12 | var body: some ReducerOf { 13 | EmptyReducer() 14 | } 15 | } 16 | 17 | struct SyncUpFormView: View { 18 | var body: some View { 19 | Form { 20 | Section { 21 | TextField("Title", text: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Design@*/.constant("Design")/*@END_MENU_TOKEN@*/) 22 | HStack { 23 | Slider(value: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=15 minutes@*/.constant(Double(15))/*@END_MENU_TOKEN@*/, in: 5...30, step: 1) { 24 | Text("Length") 25 | } 26 | Spacer() 27 | Text(/*@START_MENU_TOKEN@*//*@PLACEHOLDER=15 minutes@*/Duration.seconds(15 * 60)/*@END_MENU_TOKEN@*/.formatted(.units())) 28 | } 29 | ThemePicker(selection: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=bubblegum@*/.constant(.bubblegum)/*@END_MENU_TOKEN@*/) 30 | } header: { 31 | Text("Sync-up Info") 32 | } 33 | Section { 34 | ForEach(/*@START_MENU_TOKEN@*//*@PLACEHOLDER=$attendees@*/SyncUp.mock.attendees.map(Binding.constant)/*@END_MENU_TOKEN@*/) { $attendee in 35 | TextField("Name", text: $attendee.name) 36 | } 37 | .onDelete { indices in 38 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 39 | } 40 | 41 | Button("New attendee") { 42 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 43 | } 44 | } header: { 45 | Text("Attendees") 46 | } 47 | } 48 | } 49 | } 50 | 51 | #Preview { 52 | NavigationStack { 53 | SyncUpFormView() 54 | } 55 | } 56 | 57 | struct ThemePicker: View { 58 | @Binding var selection: Theme 59 | 60 | var body: some View { 61 | Picker("Theme", selection: $selection) { 62 | ForEach(Theme.allCases) { theme in 63 | ZStack { 64 | RoundedRectangle(cornerRadius: 4) 65 | .fill(theme.mainColor) 66 | Label(theme.name, systemImage: "paintpalette") 67 | .padding(4) 68 | } 69 | .foregroundColor(theme.accentColor) 70 | .fixedSize(horizontal: false, vertical: true) 71 | .tag(theme) 72 | } 73 | } 74 | } 75 | } 76 | 77 | extension Duration { 78 | fileprivate var minutes: Double { 79 | get { Double(components.seconds / 60) } 80 | set { self = .seconds(newValue * 60) } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /starting-point/TrySyncUps/SyncUpsList.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @Observable 4 | class SyncUpsListModel { 5 | var syncUps: [SyncUp] = [] 6 | 7 | init(syncUps: [SyncUp] = []) { 8 | self.syncUps = syncUps 9 | } 10 | 11 | func onDelete(_ indexSet: IndexSet) { 12 | self.syncUps.remove(atOffsets: indexSet) 13 | } 14 | 15 | func syncUpTapped(id: SyncUp.ID) { 16 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 17 | } 18 | 19 | func addSyncUpButtonTapped() { 20 | /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Do something...@*//*@END_MENU_TOKEN@*/ 21 | } 22 | } 23 | 24 | struct SyncUpsListView: View { 25 | let model: SyncUpsListModel 26 | 27 | var body: some View { 28 | List { 29 | ForEach(model.syncUps) { syncUp in 30 | Button { 31 | model.syncUpTapped(id: syncUp.id) 32 | } label: { 33 | CardView(syncUp: syncUp) 34 | } 35 | .listRowBackground(syncUp.theme.mainColor) 36 | } 37 | .onDelete { indexSet in 38 | model.onDelete(indexSet) 39 | } 40 | } 41 | .toolbar { 42 | Button { 43 | model.addSyncUpButtonTapped() 44 | } label: { 45 | Image(systemName: "plus") 46 | } 47 | } 48 | .navigationTitle("Daily Sync-ups") 49 | } 50 | } 51 | 52 | #Preview { 53 | NavigationStack { 54 | SyncUpsListView(model: SyncUpsListModel()) 55 | } 56 | } 57 | 58 | struct CardView: View { 59 | let syncUp: SyncUp 60 | 61 | var body: some View { 62 | VStack(alignment: .leading) { 63 | Text(syncUp.title) 64 | .font(.headline) 65 | Spacer() 66 | HStack { 67 | Label("\(syncUp.attendees.count)", systemImage: "person.3") 68 | Spacer() 69 | Label(syncUp.duration.formatted(.units()), systemImage: "clock") 70 | .labelStyle(.trailingIcon) 71 | } 72 | .font(.caption) 73 | } 74 | .padding() 75 | .foregroundColor(syncUp.theme.accentColor) 76 | } 77 | } 78 | 79 | struct TrailingIconLabelStyle: LabelStyle { 80 | func makeBody(configuration: Configuration) -> some View { 81 | HStack { 82 | configuration.title 83 | configuration.icon 84 | } 85 | } 86 | } 87 | 88 | extension LabelStyle where Self == TrailingIconLabelStyle { 89 | static var trailingIcon: Self { Self() } 90 | } 91 | -------------------------------------------------------------------------------- /starting-point/TrySyncUpsTests/RecordMeetingsTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class RecordMeetingTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /starting-point/TrySyncUpsTests/SyncUpDetailTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpDetailTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /starting-point/TrySyncUpsTests/SyncUpFormTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpFormTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /starting-point/TrySyncUpsTests/SyncUpsListTests.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import XCTest 3 | 4 | @testable import TrySyncUps 5 | 6 | final class SyncUpsListTests: XCTestCase { 7 | @MainActor 8 | func testBasics() async { 9 | let store = TestStore(initialState: SyncUpsListFeature.State(syncUps: [.mock, .productMock])) { 10 | SyncUpsListFeature() 11 | } 12 | } 13 | 14 | @MainActor 15 | func testModel() { 16 | let model = SyncUpsListModel(syncUps: [.mock, .productMock]) 17 | model.onDelete([1]) 18 | XCTAssertEqual(model.syncUps, [.mock]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /starting-point/TrySyncUpsTests/TrySyncUps.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "D568F12C-3DAF-4491-BA1B-B14568030DB9", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "targetForVariableExpansion" : { 13 | "containerPath" : "container:TrySyncUps.xcodeproj", 14 | "identifier" : "DC531C432BA3CF1E005C7270", 15 | "name" : "TrySyncUps" 16 | } 17 | }, 18 | "testTargets" : [ 19 | { 20 | "target" : { 21 | "containerPath" : "container:TrySyncUps.xcodeproj", 22 | "identifier" : "DC531C532BA3CF20005C7270", 23 | "name" : "TrySyncUpsTests" 24 | } 25 | } 26 | ], 27 | "version" : 1 28 | } 29 | --------------------------------------------------------------------------------