From 42424fd41347e0cacc3f72cb88fbde4c65e75f7e Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 12 May 2022 21:52:27 +0200 Subject: [PATCH] [313] Project Structure & TCA Code Consistency Document (#314) - the TCA XCode template - the doc describing the rules for the store and view files, project is WIP [313] Project Structure & TCA Code Consistency Document - project structure section [313] Project Structure & TCA Code Consistency Document - correct TCA xctemplate link [313] Project Structure & TCA Code Consistency Document (314) - placeholders updated to the right section [313] Project Structure & TCA Code Consistency Document (314) - comments solved - TCA Xcode template extended to offer standalone vs. combined reducers --- CODE_STRUCTURE.md | 179 ++++++++++++++++++ .../Router.xctemplate/TemplateIcon.png | Bin 1707 -> 0 bytes .../Router.xctemplate/TemplateIcon@2x.png | Bin 4249 -> 0 bytes .../Router.xctemplate/TemplateInfo.plist | 17 -- .../___FILEBASENAME___Router.swift | 19 -- .../TemplateIcon.png | Bin 2622 -> 0 bytes .../TemplateIcon@2x.png | Bin 6664 -> 0 bytes .../TemplateInfo.plist | 16 -- .../___FILEBASENAME___Screen.swift | 23 --- .../___FILEBASENAME___ScreenViewModel.swift | 9 - .../___FILEBASENAME___Store.swift | 71 +++++++ .../___FILEBASENAME___Store.swift | 79 ++++++++ .../___FILEBASENAME___View.swift | 20 ++ .../___FILEBASENAME___Store.swift | 64 +++++++ .../___FILEBASENAME___Store.swift | 72 +++++++ .../___FILEBASENAME___View.swift | 20 ++ .../___FILEBASENAME___Store.swift | 85 +++++++++ .../___FILEBASENAME___Store.swift | 93 +++++++++ .../___FILEBASENAME___View.swift | 20 ++ .../___FILEBASENAME___Store.swift | 78 ++++++++ .../___FILEBASENAME___Store.swift | 86 +++++++++ .../___FILEBASENAME___View.swift | 20 ++ xctemplates/TCA.xctemplate/TemplateInfo.plist | 102 ++++++++++ 23 files changed, 989 insertions(+), 84 deletions(-) create mode 100644 CODE_STRUCTURE.md delete mode 100644 xctemplates/Router Templates/Router.xctemplate/TemplateIcon.png delete mode 100644 xctemplates/Router Templates/Router.xctemplate/TemplateIcon@2x.png delete mode 100644 xctemplates/Router Templates/Router.xctemplate/TemplateInfo.plist delete mode 100644 xctemplates/Router Templates/Router.xctemplate/___FILEBASENAME___Router.swift delete mode 100644 xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateIcon.png delete mode 100644 xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateIcon@2x.png delete mode 100644 xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateInfo.plist delete mode 100644 xctemplates/Router Templates/SwiftUI Screen.xctemplate/___FILEBASENAME___Screen.swift delete mode 100644 xctemplates/Router Templates/SwiftUI Screen.xctemplate/___FILEBASENAME___ScreenViewModel.swift create mode 100644 xctemplates/TCA.xctemplate/EmptyCombined/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift create mode 100644 xctemplates/TCA.xctemplate/EmptyStandalone/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift create mode 100644 xctemplates/TCA.xctemplate/NavigationCombined/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift create mode 100644 xctemplates/TCA.xctemplate/NavigationStandalone/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift create mode 100644 xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift create mode 100644 xctemplates/TCA.xctemplate/TemplateInfo.plist diff --git a/CODE_STRUCTURE.md b/CODE_STRUCTURE.md new file mode 100644 index 0000000..0eceff7 --- /dev/null +++ b/CODE_STRUCTURE.md @@ -0,0 +1,179 @@ +# Code Consistency & Project Structure +In our project the syntax check is done by [swiftlint]. However, the architecture we use is the [TCA] (The Composable Architecture) and therefore we need to follow some basic principles and rules to hold structure of the files as well as project. This document describes rules for the code as well as project files/folders structure we agreed on. We encourage you to kindly follow these principles. + +# Structure of the TCA Store file +For the Store and View as well as folder name we focus on features. So the is the bare bone of the TCA code, uppercased! + +## Store file +The TCA consists of 5 blocks (state, action, environment, reducer and store). The TCA code lives in the Store file. We defined the following order of sections in this file: +1. Typealiases for the Reducer, Store and a ViewStore. +2. State +3. Action +4. Environment - always present even when no dependencies are defined = never use Void +5. Reducer holding private `Hashable` Ids, always use `static let default` reducer. Try to avoid `default: return .none` for the actions, we want the switch exhaustiveness check. +6. Store +7. Viewstore +8. Placeholders + +Any extension needed is defined under the relevant section except placeholders, those are separated at the very end of the file. Example: + +```swift +// MARK: - State + +struct State: Equatable { } + +extension State { + // anything but placeholder +} + +// MARK: - Action +// ... +``` + +## Structure of Store & View files + +Use the appropriate name for a feature you want to build. The must be bare bone of any TCA definition including the folder encapsulating the swift file(s). Example: + +```swift +// Project structure +// Folder woth following files: + +// Store.swift +struct State: Equatable { } +enum Action: Equatable { } +struct Environment + +// View.swift +struct : View { + let store: Store + + var body: some View { + WithViewStore(store) { viewStore in EmptyView() } + } +} + +struct _Previews: PreviewProvider { + static var previews: some View { + (store: .placeholder) + } +} +``` + +Do not omit Previews! + +## Navigation +Navigation is done using routes. Such mechanism consists of Route definition + state and Route action to update the state. +```swift +struct State +enum Route { + case +} +var route: Route? +} + +enum Action { + case updateRoute(State.Route?) +} +``` + +## XCode Template +We created a TCA template that automatically prepares the TCA files and code with the rules defined in this document. You can download it [here]. + +To add them to Xcode, follow the next steps: +1. open Finder +2. press cmd + shift + G +3. paste `/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates` +4. copy paste the TCA.xctemplate to the `MultiPlatform/Source/` + +The template is customizable and consists of 3 sections: + +1. TCA Store text field. This is essentially the +2. View selector of either +a. Empty (= TCA file with no navigation included) +b. Navigation (= TCA file with the navigation) +3. Reducer selector of either +a. Standalone (= only simple `default` reducer is created) +b. Combined (= the `default` reducer is created so it combines more reducers) +4. Checkbox which allows you to create also a View file if requested. + +## Project Structure +The project follows the structure detailed below: +``` +Models + .swift +Wrappers + Wrapped.swift +Utils + .swift +Dependencies + .swift +Features + + Store.swift + View.swift + Views (optional) +UI Components + + .swift +Resources + Generated (by swiftgen) + Fonts + +``` + +## Models +Here are the models used in the project, usually data containers shared across several places. +Examples: +- Transaction struct - holding the data for the transaction +- StoredWallet struct - holding the secured and sensitive data for the wallet + +## Wrappers +Here are the wrapped modules either from the SDK, iOS itself or wallet custom modules. Purpose of wrappers is to provide `live` implementation and other versions used testing purposes, usually one of the following: +- `mock` - mocked data +- `throwing` - throwing errors +- `test` - specific setup for particular tests + +Examples of the wrappers: +- SDK: Mnemonic, DerivingTool, SDKSynchronizer, ... +- iOS: FileManager, UserDefaults, NotificationCenter, ... +- wallet: DatabaseFiles, WalletStorage, ... + +## Utils +Here are the utilities used in the project. Usually helpers or extensions of the build in types. +Examples: +- helpers: navigation links, bindings, view modifiers, ... +- types: `String` extensions for the conversions, `UInt`/`Int64` extensions to handle balance computations, etc.``` + +## Dependencies +Modules used as dependencies by the reducers are located here. Some dependencies are already living in the SDK and only a wrapper is implemented (stored in the wrappers folder) for it. Sometimes the dependency is implemented in a way that it doesn't need to be wrapped (typically it uses some wrapped helper). In that case, the `live` vs. `mocked` static instances are implemented directly within the same file as the dependency. + +## Features vs. UI Components +We distinguish between smaller building blocks used in the composed complex UIs vs. screens and flows - understand features. Essentially we talk about two topics: +1. Features: typically views representing entire screeens, the whole flows, complex UIs and business logic that are usually not shared or used many times. +2. UI Components: smaller building blocks typically used across the application and features, like custom controls (buttons, input fields) or visual elements. Even when used just once, the nature of a UI component is to be reused so it's not forming standalone feature. + +Examples: +- features: Send Flow, Onboarding Flow, Settings Screen, Scan Screen, Profile Screen, ... +- UI Components: buttons, chips, button styles, view modifiers, drawer, custom textfields, shapes, ... + + When implementing something new, just ask yourself "Am I building something that can be shared and used several times or am I building a standalone feature?". + +In case of a *flow feature* the following structure is used: +```swift +Flow + FlowStore.swift + FlowView.swift + Views + View.swift + View.swift + View.swift +``` + +## Resources +All project resources should be placed in this folder. Images, fonts, generated files (by swiftgen for example), sounds, assets, string files, ... + +[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax) + +[TCA]: +[swiftlint]: +[here]: diff --git a/xctemplates/Router Templates/Router.xctemplate/TemplateIcon.png b/xctemplates/Router Templates/Router.xctemplate/TemplateIcon.png deleted file mode 100644 index 4109abfa5e7b0c0971b498baf3cd7ee5cb5b2d50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1707 zcmV;c22}ZpP) z990zl?wy%rlO`rjtF{qq(l#x`YQ<73VlfdaEsE(wK`B)zzF1#W3O@K?TSY{CXiEi) zP%9LC(Fgk=_D5(yL|PCdiV9+l)D*2LjWx~gW_Ra$&Y8QD{Y$emJC^2y*`1j?bMN`? z`MKwwU4;r9+MFBkj2(To6gDeZ{x+&?Kt-Rb2Zzi@)P)K>;rsx;V{P2|AUg19wHMe z7{tU2Cq1%9U(VkexPXq-Qi=*eVEb|W6pnni9aFh0;q#BLcnweVC980a7+|4&Q-G&a z;l0V+C2o+m%jr?P|LwLi;TfO(`Z|6X{~SpoG64gh4*E1Xw7Q+){eQB5p4o-xpC4`L5L;+H`3Dv zGW$Gu!OvlV*A+IU$zcnm6@vnLJJ!$IMR(g8dLjo!xeBdFq0hucBn`0W@@;$(bV1V$ zfos#3nHCW%pr>;K?z&}N)%%^z-FRf#^YHyZQ$Xza5h9`wSQ|p%tSRS~u;r)m)0jw) ziEprZJ#)_ytnc2AMJ+vOZ(5ACw>^nx?>&n4<|QJ$vHdiP$YGQR&-)p1(sLI-!Y4oP z4%-%{?!=ZAuVT%uTQFUiqT?DpJ1r*X=q5Bdt!Q@Ih_du~c|hNW*trStte0I=YGD+u z&NLxWbE5_bFqAbXBidk-lBhK2Yvq>f+Y~(x8e0ZK1U|tUQEKikb>*<8lt45hi4l;a z&h%Kpsc}!mO79XO5DIPT6F{liNZz0@T0N{CxQ1X$+xjJ?E=84=RFGc8L2??9@lBNh zZJ?xW-I8r{_n}Jqih$%aP~7WOg8ib(u?<$*oT-2DCX^e0G)*)ZMgyp_lkyqV*10R+ zc&h&ZCNdYXq-}NJ6Dr=OCJ_SVD4#*jn<_Ug!78q?xOKVJj$zGO^_RIhCIomBn?4yBT;D&AMo}?|T!C(-())_ehtRw5L8=u! zjFnv*sT{uuk1c=2zM|}Rd<`gNMyRxy8z4oTK?4YCrg==;nrSvaiM+|!Ut(Cmz>L^& zIsT{IHYxP-xg$|1E~b0+t`pucY2hFmKxhnh)s-kCvE2#x$vQlo@L5>;v0(gz{J#n5 zC6jjdQluC`gOH-56ug3kihl&;;+I^@ce70+Tp;v==sv)IHArRMy}_7-mxt9vI;#fb zYoa*&j?_z~Vn_A=nWI%qrnCSyvkpWA&3y5H62Zt7;J+Tw&qsT;QEMrD*Zm{7v2`I+ zth5#FKU^CEUtqzdf*~a8E@F+6;K%9FPsQX*@E-}SFE_YY5QYE%002ovPDHLkV1h2u BAo>6R diff --git a/xctemplates/Router Templates/Router.xctemplate/TemplateIcon@2x.png b/xctemplates/Router Templates/Router.xctemplate/TemplateIcon@2x.png deleted file mode 100644 index 243d71d802bb0bceb8241e6037cadabf7b3bdd0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4249 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 diff --git a/xctemplates/Router Templates/Router.xctemplate/TemplateInfo.plist b/xctemplates/Router Templates/Router.xctemplate/TemplateInfo.plist deleted file mode 100644 index 57045b4..0000000 --- a/xctemplates/Router Templates/Router.xctemplate/TemplateInfo.plist +++ /dev/null @@ -1,17 +0,0 @@ -{ - Kind = "Xcode.IDEFoundation.TextSubstitutionFileTemplateKind"; - Platforms = ( - "com.apple.platform.iphoneos", - "com.apple.platform.macosx", - ); - Options = ( - { - Description = "The name of the module to create"; - Identifier = productName; - Name = Router; - Required = YES; - Type = text; - Default = Router; - }, - ); -} diff --git a/xctemplates/Router Templates/Router.xctemplate/___FILEBASENAME___Router.swift b/xctemplates/Router Templates/Router.xctemplate/___FILEBASENAME___Router.swift deleted file mode 100644 index f39c22f..0000000 --- a/xctemplates/Router Templates/Router.xctemplate/___FILEBASENAME___Router.swift +++ /dev/null @@ -1,19 +0,0 @@ -//___FILEHEADER___ - -import Foundation -import SwiftUI - -class ___FILEBASENAMEASIDENTIFIER___: Router { - var services: Services - - init(services: Services) { - self.services = services - } - - func rootView() -> some View { - // Add your content here - NavigationView { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } - } -} diff --git a/xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateIcon.png b/xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateIcon.png deleted file mode 100644 index e40618ef63468a2ffe070694a2982f2653e4d401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2622 zcmV-E3c>Y>P)L8bpXY6D|e<(Ry^|tzn`xf3u&xTN#)sb)>h!gk-womYtBn_Ovtz#D8 zp&1)7SnNi^sX;PUi~eF48j{T@ln1ouAr-GjL1uB}{7z=vuoa1kmN=>;Wx-2T=I=x- z1y>fZ{-5mxeiXhq?I-wM$HVx>jAyX_+>7WboWW1${TmKqa8-%la}c*msGfVIUWJWhP-c4rHfX37$`ZV)%rR=-Xh3mMa523B=>NB9FuL!)P zZ@$)`2;JY_Ux#!&gZ7)gk8Q_(jAhe*iGOu%Lr?w;W;89bGr=13#!;wB22{n^yV8RE z(zG@FJ*ECm2H)B^2cuJykuCQczcKoQ<1jis8OMA7 zf#%E|XdOM*O5BS&5yNMQ97Lh8@mgF*x_9H~`Q5mXJ&Cu;bS~sh!NAt+ITm}sG=_|> zl&aEzZ$O?$>O0mFR^6i$l!GKW@uzM$hBQSaMT{yXhDcuwS@9~GkQfHSizEVZCuJoq z2z-V^r4E!zI2CVrup@;aRIP!KSV0E|#nK+PQm<=QCID=;3`kC-DZQ0Km{t}2DkE-;6 z&`5>KGHve!JZ70N=51_WF?gqP6*ix1-~$1L4ZsA-y{ES@BeTYT8GBAYhr1>`gj0P7 zFj(rr{3(xBzAilTy9S9Yz!2V8;I&g5sgN6ohSWstKK?W&jJgf`J73~M&%IMthUO}t z@hB864_MN&&z5`m$?7?KsjMzh@pdEDef}6~<7069*hPF!(AhXknlSz$xB!HRRu4t= z0aJyJp=09lp1Ib3?X7$}~{(e7QCT>t4ytaxPwLs@H(7bLjP$-rP~T)S>@wh50Z*g!SPix&$BYh4AEe)t%Uw z5JMEq0<}^cn{8Fa8BK^#)eT#2XfJRQrfR(wc_0Aig-eIaW#u7dTQ|T=$s8_Ph1HDX zJAD+oBm*_0s1?{s-*v#i2SE4nVJz@>jJ4o!Qqnx=`OR$tC-zBZBv}h2>ja7y!Jits zE@sW?EHjSAgVbQYXx7Y(H(U>VXr5`(<11!BFZ7k4J7z&k2e&F5d|`MV4*%4E$4&zl zuW_*L35oom!h@S)ER2I&C4;$Zxr42bNYoR+Z$9tf+&jRI)soj}|B*w?1Dh91w678@ zh!-A}EWnXy#3-a~_}2Rj6K_3R#>r|=sr{&6Y9{LA+50B4T|xuOcG_IL{ntG9 z{VB)yW`8w~rN2mF#UE4r{On>c=l)kPq-rSty@teQS z;?Jx4aprKDueU7k!+$&Sz6NyH&VV+OE>oXX*#nTv4YV>widar1{D6s;In)mCEMm)d zI`QHUKjh8o#|yeIar#h+Oi19Cg@OTVso%2DtZ`&lp5JE>RA#uc>;o#woDZrb^K`Gs zW8I=G>#~HdYphw7xQ}Y)YVeXffie`LM_|B<;dITePkwIdVc1E*UcA$j! z-pca0b#4sr?#NI=S%Hg`1M+nXoJ7HDH*_fl%f!Aj9(JMSU}+favx*N?}m&s@N(oBoH- zJ<^B;k7anCX6P?J>tcpsHK3RQ#}{!pcT`lPo-=86YiVhf$7@}R>xvczn~K+M;Q7J; z^BPvBHDr>Lr*4zmq%Ryk$k5L;c<(=Ss?7G|6K_#Tz0p&dDE$@R6w$2gKHV?cC)Py}D9G?kK{*G5^ zS|EGhUWWrg86F5*5d#X_Y5b(Qp`|WudxR=YM_R?l z1BrSY05;lqoe8snIh?2w&&`1j;5#L(xmC>b&kW%GboDAkP(KBW7uWHsn-`Zh7%doy zn6TxtBQZ#;x^NK&3qVyGOsGW02z?)G1{%1qCpCM}F_KclOwrS-<9GY)`butGxhwG$ zN1=Q!zBXN>?$y7s^y++Au)dm=wZ{O*uS+RJy!2tO>KU3;__KQrU)FrR?bnJ_zr<%2GLeN=D@&L4=q@@H;aF z3}Z-3K2?aXt@vY)FzG)%pkTVzQDPQJe|E4#Sezr(@R#eZ4SDW^9=W<5^3F*zgdG%6 gwy)focq2Uf|LGfdsgTSNIRF3v07*qoM6N<$g1&$OEdT%j diff --git a/xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateIcon@2x.png b/xctemplates/Router Templates/SwiftUI Screen.xctemplate/TemplateIcon@2x.png deleted file mode 100644 index beb319f106dc864c62020e5e276b0271e56b817a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6664 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 SOY;c;0000, ObservableObject { - - -} diff --git a/xctemplates/TCA.xctemplate/EmptyCombined/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/EmptyCombined/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..de3dfeb --- /dev/null +++ b/xctemplates/TCA.xctemplate/EmptyCombined/___FILEBASENAME___Store.swift @@ -0,0 +1,71 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer.combine( + [ + reducer + ] + ) + .debug() + + private static let reducer = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} diff --git a/xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..6e4041d --- /dev/null +++ b/xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift @@ -0,0 +1,79 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer.combine( + [ + reducer + ] + ) + .debug() + + private static let reducer = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} + +extension ___VARIABLE_productName:identifier___Store { + static let placeholder = ___VARIABLE_productName:identifier___Store( + initialState: .placeholder, + reducer: .default, + environment: ___VARIABLE_productName:identifier___Environment() + ) +} diff --git a/xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift b/xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift new file mode 100644 index 0000000..d82c5e9 --- /dev/null +++ b/xctemplates/TCA.xctemplate/EmptyCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift @@ -0,0 +1,20 @@ +//___FILEHEADER___ + +import SwiftUI +import ComposableArchitecture + +struct ___VARIABLE_productName:identifier___: View { + let store: ___VARIABLE_productName:identifier___Store + + var body: some View { + WithViewStore(store) { viewStore in + Text("Hello, World!") + } + } +} + +struct ___VARIABLE_productName:identifier____Previews: PreviewProvider { + static var previews: some View { + ___VARIABLE_productName:identifier___(store: .placeholder) + } +} diff --git a/xctemplates/TCA.xctemplate/EmptyStandalone/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/EmptyStandalone/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..3297544 --- /dev/null +++ b/xctemplates/TCA.xctemplate/EmptyStandalone/___FILEBASENAME___Store.swift @@ -0,0 +1,64 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} diff --git a/xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..d20b501 --- /dev/null +++ b/xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift @@ -0,0 +1,72 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} + +extension ___VARIABLE_productName:identifier___Store { + static let placeholder = ___VARIABLE_productName:identifier___Store( + initialState: .placeholder, + reducer: .default, + environment: ___VARIABLE_productName:identifier___Environment() + ) +} diff --git a/xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift b/xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift new file mode 100644 index 0000000..d82c5e9 --- /dev/null +++ b/xctemplates/TCA.xctemplate/EmptyStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift @@ -0,0 +1,20 @@ +//___FILEHEADER___ + +import SwiftUI +import ComposableArchitecture + +struct ___VARIABLE_productName:identifier___: View { + let store: ___VARIABLE_productName:identifier___Store + + var body: some View { + WithViewStore(store) { viewStore in + Text("Hello, World!") + } + } +} + +struct ___VARIABLE_productName:identifier____Previews: PreviewProvider { + static var previews: some View { + ___VARIABLE_productName:identifier___(store: .placeholder) + } +} diff --git a/xctemplates/TCA.xctemplate/NavigationCombined/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/NavigationCombined/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..78c3022 --- /dev/null +++ b/xctemplates/TCA.xctemplate/NavigationCombined/___FILEBASENAME___Store.swift @@ -0,0 +1,85 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture +import SwiftUI + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + enum Route: Equatable { + } + + var route: Route? +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + case updateRoute(___VARIABLE_productName:identifier___State.Route?) +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer.combine( + [ + reducer + ] + ) + .debug() + + private static let reducer = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + case .updateRoute(let route): + state.route = route + return .none + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + func bindingForRoute(_ route: ___VARIABLE_productName:identifier___State.Route) -> Binding { + self.binding( + get: { $0.route == route }, + send: { isActive in + return .updateRoute(isActive ? route : nil) + } + ) + } +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} diff --git a/xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..157ce02 --- /dev/null +++ b/xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift @@ -0,0 +1,93 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture +import SwiftUI + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + enum Route: Equatable { + } + + var route: Route? +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + case updateRoute(___VARIABLE_productName:identifier___State.Route?) +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer.combine( + [ + reducer + ] + ) + .debug() + + private static let reducer = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + case .updateRoute(let route): + state.route = route + return .none + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + func bindingForRoute(_ route: ___VARIABLE_productName:identifier___State.Route) -> Binding { + self.binding( + get: { $0.route == route }, + send: { isActive in + return .updateRoute(isActive ? route : nil) + } + ) + } +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} + +extension ___VARIABLE_productName:identifier___Store { + static let placeholder = ___VARIABLE_productName:identifier___Store( + initialState: .placeholder, + reducer: .default, + environment: ___VARIABLE_productName:identifier___Environment() + ) +} diff --git a/xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift b/xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift new file mode 100644 index 0000000..d82c5e9 --- /dev/null +++ b/xctemplates/TCA.xctemplate/NavigationCombinedgenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift @@ -0,0 +1,20 @@ +//___FILEHEADER___ + +import SwiftUI +import ComposableArchitecture + +struct ___VARIABLE_productName:identifier___: View { + let store: ___VARIABLE_productName:identifier___Store + + var body: some View { + WithViewStore(store) { viewStore in + Text("Hello, World!") + } + } +} + +struct ___VARIABLE_productName:identifier____Previews: PreviewProvider { + static var previews: some View { + ___VARIABLE_productName:identifier___(store: .placeholder) + } +} diff --git a/xctemplates/TCA.xctemplate/NavigationStandalone/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/NavigationStandalone/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..56463db --- /dev/null +++ b/xctemplates/TCA.xctemplate/NavigationStandalone/___FILEBASENAME___Store.swift @@ -0,0 +1,78 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture +import SwiftUI + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + enum Route: Equatable { + } + + var route: Route? +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + case updateRoute(___VARIABLE_productName:identifier___State.Route?) +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + case .updateRoute(let route): + state.route = route + return .none + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + func bindingForRoute(_ route: ___VARIABLE_productName:identifier___State.Route) -> Binding { + self.binding( + get: { $0.route == route }, + send: { isActive in + return .updateRoute(isActive ? route : nil) + } + ) + } +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} diff --git a/xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift b/xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift new file mode 100644 index 0000000..ac61035 --- /dev/null +++ b/xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___Store.swift @@ -0,0 +1,86 @@ +//___FILEHEADER___ + +import Foundation +import ComposableArchitecture +import SwiftUI + +typealias ___VARIABLE_productName:identifier___Reducer = Reducer<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action, ___VARIABLE_productName:identifier___Environment> +typealias ___VARIABLE_productName:identifier___Store = Store<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> +typealias ___VARIABLE_productName:identifier___ViewStore = ViewStore<___VARIABLE_productName:identifier___State, ___VARIABLE_productName:identifier___Action> + +// MARK: - State + +struct ___VARIABLE_productName:identifier___State: Equatable { + enum Route: Equatable { + } + + var route: Route? +} + +// MARK: - Action + +enum ___VARIABLE_productName:identifier___Action: Equatable { + case updateRoute(___VARIABLE_productName:identifier___State.Route?) +} + +// MARK: - Environment + +struct ___VARIABLE_productName:identifier___Environment { + +} + +extension ___VARIABLE_productName:identifier___Environment { + static let live = ___VARIABLE_productName:identifier___Environment( + ) + + static let mock = ___VARIABLE_productName:identifier___Environment( + ) +} + +// MARK: - Reducer + +extension ___VARIABLE_productName:identifier___Reducer { + static let `default` = ___VARIABLE_productName:identifier___Reducer { state, action, environment in + switch action { + case .updateRoute(let route): + state.route = route + return .none + } + + return .none + } +} + +// MARK: - Store + +extension ___VARIABLE_productName:identifier___Store { + +} + +// MARK: - ViewStore + +extension ___VARIABLE_productName:identifier___ViewStore { + func bindingForRoute(_ route: ___VARIABLE_productName:identifier___State.Route) -> Binding { + self.binding( + get: { $0.route == route }, + send: { isActive in + return .updateRoute(isActive ? route : nil) + } + ) + } +} + +// MARK: - Placeholders + +extension ___VARIABLE_productName:identifier___State { + static let placeholder = ___VARIABLE_productName:identifier___State( + ) +} + +extension ___VARIABLE_productName:identifier___Store { + static let placeholder = ___VARIABLE_productName:identifier___Store( + initialState: .placeholder, + reducer: .default, + environment: ___VARIABLE_productName:identifier___Environment() + ) +} diff --git a/xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift b/xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift new file mode 100644 index 0000000..d82c5e9 --- /dev/null +++ b/xctemplates/TCA.xctemplate/NavigationStandalonegenerateViewFile/___FILEBASENAME___/___FILEBASENAME___View.swift @@ -0,0 +1,20 @@ +//___FILEHEADER___ + +import SwiftUI +import ComposableArchitecture + +struct ___VARIABLE_productName:identifier___: View { + let store: ___VARIABLE_productName:identifier___Store + + var body: some View { + WithViewStore(store) { viewStore in + Text("Hello, World!") + } + } +} + +struct ___VARIABLE_productName:identifier____Previews: PreviewProvider { + static var previews: some View { + ___VARIABLE_productName:identifier___(store: .placeholder) + } +} diff --git a/xctemplates/TCA.xctemplate/TemplateInfo.plist b/xctemplates/TCA.xctemplate/TemplateInfo.plist new file mode 100644 index 0000000..3e4a9de --- /dev/null +++ b/xctemplates/TCA.xctemplate/TemplateInfo.plist @@ -0,0 +1,102 @@ + + + + + SupportsSwiftPackage + + Kind + Xcode.IDEFoundation.TextSubstitutionFileTemplateKind + Description + The Composable Architecture pre-filled swift file. + Summary + The Composable Architecture pre-filled swift file. + SortOrder + 1 + Image + + FileTypeIcon + swift + + AllowedTypes + + public.swift-source + + Platforms + + DefaultCompletionName + File + Options + + + Description + The name of the module to create + Identifier + productName + Name + TCA Store + Required + YES + Type + text + Default + + + + Identifier + viewType + Required + + Name + View type: + Description + The type of store to create + Type + popup + Default + Empty + Values + + Empty + Navigation + + + + Identifier + reducerType + Required + + Name + Reducer type: + Description + The type of the reducer to be pre-generated. + Type + popup + Default + Standalone + Values + + Standalone + Combined + + + + Identifier + generateViewFile + Name + Also create a view TCA file + Description + Create a TCA file with the same name + Type + checkbox + RequiredOptions + + viewType + + Empty + Navigation + + + + + +