From d5b58125364bc2717d527a6553aadacaa3d711cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Thu, 26 Oct 2023 17:24:20 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20onboarding=20scene=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- D3N.entitlements | 10 +++ Targets/D3N/Sources/App/RootStore.swift | 7 +- Targets/D3N/Sources/App/RootView.swift | 4 ++ .../Feature/MainTab/MainTabStore.swift | 1 - .../Nickname/OnboardingNicknameStore.swift | 39 +++++++++++ .../Nickname/OnboardingNicknameView.swift | 24 +++++++ .../OnboardingNavigationStackStore.swift | 68 +++++++++++++++++++ .../OnboardingNavigationStackView.swift | 39 +++++++++++ .../SignUp/OnboardingSignUpStore.swift | 39 +++++++++++ .../SignUp/OnboardingSignUpView.swift | 27 ++++++++ .../UserInfo/OnboardingUserInfoStore.swift | 9 +++ .../UserInfo/OnboardingUserInfoView.swift | 9 +++ 12 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 D3N.entitlements create mode 100644 Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift create mode 100644 Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift diff --git a/D3N.entitlements b/D3N.entitlements new file mode 100644 index 0000000..a812db5 --- /dev/null +++ b/D3N.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/Targets/D3N/Sources/App/RootStore.swift b/Targets/D3N/Sources/App/RootStore.swift index f72f180..522b94c 100644 --- a/Targets/D3N/Sources/App/RootStore.swift +++ b/Targets/D3N/Sources/App/RootStore.swift @@ -12,14 +12,17 @@ import ComposableArchitecture struct RootStore: Reducer { enum State: Equatable { + case onboarding(OnboardingNavigationStackStore.State) case mainTab(MainTabStore.State) init() { + self = .onboarding(.init()) self = .mainTab(.init()) } } enum Action: Equatable { + case onboarding(OnboardingNavigationStackStore.Action) case mainTab(MainTabStore.Action) } @@ -29,7 +32,9 @@ struct RootStore: Reducer { default: return .none } } - + .ifCaseLet(/State.onboarding, action: /Action.onboarding) { + OnboardingNavigationStackStore() + } .ifCaseLet(/State.mainTab, action: /Action.mainTab) { MainTabStore() } diff --git a/Targets/D3N/Sources/App/RootView.swift b/Targets/D3N/Sources/App/RootView.swift index f556ac8..8402c47 100644 --- a/Targets/D3N/Sources/App/RootView.swift +++ b/Targets/D3N/Sources/App/RootView.swift @@ -15,6 +15,10 @@ struct RootView: View { var body: some View { SwitchStore(self.store) { switch $0 { + case .onboarding: + CaseLet(/RootStore.State.onboarding, action: RootStore.Action.onboarding) { + OnboardingNavigationStackView(store: $0) + } case .mainTab: CaseLet(/RootStore.State.mainTab, action: RootStore.Action.mainTab) { MainTabView(store: $0) diff --git a/Targets/D3N/Sources/Feature/MainTab/MainTabStore.swift b/Targets/D3N/Sources/Feature/MainTab/MainTabStore.swift index 2257a0d..f3040ea 100644 --- a/Targets/D3N/Sources/Feature/MainTab/MainTabStore.swift +++ b/Targets/D3N/Sources/Feature/MainTab/MainTabStore.swift @@ -14,7 +14,6 @@ enum MainScene: Hashable { } struct MainTabStore: Reducer { - struct State: Equatable { var currentScene: MainScene = .question diff --git a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift new file mode 100644 index 0000000..1135af7 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift @@ -0,0 +1,39 @@ +// +// OnboardingNicknameStore.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import ComposableArchitecture + +public struct OnboardingNicknameStore: Reducer { + public struct State: Equatable { + public init() { } + } + + public enum Action: Equatable { + case onAppear + + case delegate(Delegate) + + public enum Delegate: Equatable { + case select(NewsEntity) + } + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .none + + default: + return .none + } + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift new file mode 100644 index 0000000..6263420 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift @@ -0,0 +1,24 @@ +// +// OnboardingNicknameView.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import SwiftUI + +import ComposableArchitecture + +struct OnboardingNicknameView: View { + let store: StoreOf + + var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + VStack { + Text("닉네임을 입력해주세요.") + .font(.largeTitle) + } + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift new file mode 100644 index 0000000..bd2773d --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift @@ -0,0 +1,68 @@ +// +// OnboardingNavigationStackStore.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import ComposableArchitecture + +public struct OnboardingNavigationStackStore: Reducer { + public struct State: Equatable { + var signUp: OnboardingSignUpStore.State = .init() + + var path: StackState = .init() + } + + public enum Action: BindableAction, Equatable { + case binding(BindingAction) + + case onAppear + + case signUp(OnboardingSignUpStore.Action) + case path(StackAction) + + case delegate(Delegate) + + public enum Delegate { + case complete + } + } + + public struct Path: Reducer { + public enum State: Equatable { + case nickname(OnboardingNicknameStore.State) + } + + public enum Action: Equatable { + case nickname(OnboardingNicknameStore.Action) + } + + public var body: some Reducer { + Scope(state: /State.nickname, action: /Action.nickname) { + OnboardingNicknameStore() + } + } + } + + public var body: some ReducerOf { + BindingReducer() + + Reduce { state, action in + switch action { + case .onAppear: + return .none + + default: + return .none + } + } + Scope(state: \.signUp, action: /Action.signUp) { + OnboardingSignUpStore() + } + .forEach(\.path, action: /Action.path) { + Path() + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift new file mode 100644 index 0000000..0be1035 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift @@ -0,0 +1,39 @@ +// +// OnboardingNavigationStackView.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import SwiftUI + +import ComposableArchitecture + +public struct OnboardingNavigationStackView: View { + let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + NavigationStackStore(self.store.scope( + state: \.path, + action: OnboardingNavigationStackStore.Action.path) + ) { + WithViewStore(self.store, observe: { $0 }) { viewStore in + OnboardingSignUpView(store: self.store.scope(state: \.signUp, action: OnboardingNavigationStackStore.Action.signUp)) + } + } destination: { + switch $0 { + case .nickname: + CaseLet( + /OnboardingNavigationStackStore.Path.State.nickname, + action: OnboardingNavigationStackStore.Path.Action.nickname, + then: OnboardingNicknameView.init(store:) + ) + } + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift new file mode 100644 index 0000000..fda9ad0 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift @@ -0,0 +1,39 @@ +// +// OnboardingSignUpStore.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import ComposableArchitecture + +public struct OnboardingSignUpStore: Reducer { + public struct State: Equatable { + public init() { } + } + + public enum Action: Equatable { + case onAppear + + case delegate(Delegate) + + public enum Delegate: Equatable { + case select(NewsEntity) + } + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .none + + default: + return .none + } + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift new file mode 100644 index 0000000..8b53f39 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -0,0 +1,27 @@ +// +// OnboardingSignUpView.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import SwiftUI + +import ComposableArchitecture + +public struct OnboardingSignUpView: View { + let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + VStack { + Text("") + } + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift new file mode 100644 index 0000000..0c44cb3 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift @@ -0,0 +1,9 @@ +// +// OnboardingUserInfoStore.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation diff --git a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift new file mode 100644 index 0000000..c729389 --- /dev/null +++ b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift @@ -0,0 +1,9 @@ +// +// OnboardingUserInfoView.swift +// D3N +// +// Created by 송영모 on 10/26/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation From 129294b16ea2293afcafb833372a3e24f54f8d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Thu, 26 Oct 2023 19:24:06 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=EB=B7=B0=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets.xcassets/Logo.imageset/1024.png | Bin 0 -> 62850 bytes .../Logo.imageset/Contents.json | 12 +++++ Targets/D3N/Sources/App/RootStore.swift | 2 +- .../Nickname/OnboardingNicknameStore.swift | 24 ++++++++- .../Nickname/OnboardingNicknameView.swift | 36 ++++++++++++- .../OnboardingNavigationStackStore.swift | 7 +++ .../SignUp/OnboardingSignUpStore.swift | 7 ++- .../SignUp/OnboardingSignUpView.swift | 33 +++++++++++- .../UserInfo/OnboardingUserInfoStore.swift | 50 ++++++++++++++++++ .../UserInfo/OnboardingUserInfoView.swift | 27 +++++++++- 10 files changed, 190 insertions(+), 8 deletions(-) create mode 100644 Targets/D3N/Resources/Assets.xcassets/Logo.imageset/1024.png create mode 100644 Targets/D3N/Resources/Assets.xcassets/Logo.imageset/Contents.json diff --git a/Targets/D3N/Resources/Assets.xcassets/Logo.imageset/1024.png b/Targets/D3N/Resources/Assets.xcassets/Logo.imageset/1024.png new file mode 100644 index 0000000000000000000000000000000000000000..ce123ae6eccf210a421ab7dd7af433d3e3d7c56e GIT binary patch literal 62850 zcmeEucOcd6|2~JJiL6pal#!5-9O6_W*(ocdhax0puW-^d6Cu0I2#IWt$|##d%1C7% zGkYE9{9gBYKI8lO_xJbnSJQjk_iJ3Q>$>ju6FnXEUE6uK)6voG(m1brk&ccLe#=P5 zycK@&=M@ixU+7&fs-L0DYdt`sqZ6RhP(6Lg(_(6Xsg(C}*|DE)RPoE0m)n@nJH_0h z7rR&$#G(K6hvFf8yXH9W8QqsLdTCECmErXCaM>5wNLB2tPwDVA)IsXnSGVQF5D|rq zLm~>cvtDCf^Q>2wr%Yx|WqiJteD$3iym*0{c$1NtRR9-2N00gYH@GW1$nkWzD?Rxi z+5Yd})8UT_(4`#wpZ`mT=V4~1>q>dX8S$T=r~QVR?gqpEUN>bc216&8b?w}t|8p<+ zzZkmS|NT+cEPMbRZr2eLwr&4&uK>D$6aV|8jP?Qobb5go3|0U2=>NJ9Jm#+d`O$wK z^PkoHZ;<~JoBtax>ZA(cQS z&r&I~nEv41hN(-H!7?LU@sTv4p)S>wn5P&WuvpLKugEOkVMy_eEPT~iUGkO!$7 z{#zLB`)xQ95pLhC%lEJ3-uPRi81yRob-(|Xc6&OZBf z=Occ6rS?)e(z`I`P%Z@AF6KJ{VcveXhJQRk%1+*p|V>=P9r` z7w)g+zxs*a`Kmy%)pCFlW6^P1J89SJr$RAx=dElj>Mb*3Oi8u8!MpwDst6kcvzWWt^q0B3^@2G^2}q_G^CSe4|$_m7B17E*#!+1fIw1I)4AH(u0Yfzn_pI z)cL+t)NA2?FX1)h=DhxfEh#>XjCAD<9s0DVsyVdnQq1BN``786l#SWs;$Hh4HNk4; z?HmSwL`Y5Lq)+FV2q#yh2;}4D5Za^!msX8%6^bV%`n=%YG2V^dfA0Y-+_;JVpbjl~ zxgmIk;{BFD+4>3BEqD-DFyBjU+J;~l9B_Vf+>{=VKrq4M)HY^6^{-;Rcw z&(Q*+VGARjcjfj3+5&dzqu6!(YC`nen2vG(;WZ`;xm2RnKrr*@=4AK*f9pE>!giFC zL3w>#2p@nw;d&iqEZzbISx)w+FGwrF@iJP;R(dx0qr~rXEHUSiy`dLpo8-mAOBLkC ziF;pSpwA~zys?vS3g;*3`TbV^5@OZcTV%B_@$ZbfaSry;@QvJ<7J54gF}|9Vh7wd%pa{J4X1o_TNI-Ialuhr9Mi=ar4d zsj=1s(MxnhB_2XKT~rDg8Elk76hZI$LtM|i(<%4;*GA3tTmof1G|WF@N6Jd{tBuy9 z&*Ci`@vog@Wu!5WUheH!Ix@D%S7}x=?I0reiK#e=@&%{fIyD;|{5W<$jkoT3y^{ z6MK+aGM}|_O*t_d>nkUE+kygNSod##J?fvcbnQ5|pO^ak)QTiu_16j`TSirZ zpJ|ag7se7?bgUnq^HQiYwH8zQyH>w~o#g&7MN69{uE(vm(!8gzt9`dB9I=b-XBUd7 z-?Wxyla}>E7P$*W_QkamsIb zY5fhi3v$rjm=r_{_m&+|F*(7yo5Lb18XL0&e^ZCt_pao4+l1j$IWicvumdj`=pn8= zl-=D(bsCg2On-v`M^VVYhtIRmqcu84!gNQBjKo+f@8Xdab*!AL_v$L^J(#%YGOQa{0S$ za?uyz__Tcg%}KAe@vDtfgM}>_W=wM{Y(i>j&^RTl<{QP=Y8&%!E>68zcSq4A(2mNV zx32j~c3PC4MpuJ)N*tUUbSxR@gr1f&G#Kb6^D-*<}9VtDgweCs8?{U$32m0c#>{+dU_eB%i#OIQge?A@%5H;NS zc=OPXn^o&>^|bw)gW|>RG!{u)Kr2jl`tK8cHw%kqt2Z(@4UG@R9Kt?2CmFyc9!H$U zrIcU%w7D{v9HuZD>)OEUA-f!Qf^Fmb-zA0@R&7GBDfsS&z`wl!jpres;&xn8q~zol zn_?%jQaj@u@?KmIiuh?oh;w;wK9_?J7~Guku>14#gWd4wh_W}`%N_1?+f`}(o+1Va zT#Q!d`?jN2cK!Kf1SJ-p`4w;q5wH};KE`@6<1UZpKcW`XrE~TBiBCk5V)^`5e^pvm zF6&n+>#?w7T*CqI8TfsREVC7C%3KM@~S$)_*$( zFIn*9VjZ_-8Va?-Hf6b28c(ri8M57%iNokR&az%TD@1w7qWElT@)f6E%iq1+L zVStO;OX^Lpw!!i%lnu5j&L-FZE>UYB>|M%Li1{7Em@U4wa$}}~v`Ey|9ZUA7oYZ96 z&UoVlZR2($gsB(Yh~AZFM^uj+b-jm>R6O9`=Q{rKQAXKV67=Z$=~p@D^0#6v{5O{> zU4I^qF?*)G`ZLhC)cMNf#N4zlZPC305hl#qG6t8T$Cz`8>b4~TD(y79;`p4MEb+eU zB9@YPgKM^!`^%s5kimNXtgV(X|LxLa}R>G~om zl6s{w-cf@Pwk1bnDhaTp?9Fd)U7>P}9&*24@+uYEeaRp_d2`B^^yd?cE}(4LAsxxK z>^TJP-r>(a+^LaYwQMTHDxjaTaRA+I@dT8lABIA$kKjS%IJ3f5SEl>qZI}Mrd!x97 zdgZS3I2GgwQQDLiwD?}YP?bXzN%lSdL&wg`u412Uf;5z9cTxbu?(+++iupJkd+1Tj z<#VrU;i$LzEi^!^Nqz)0r zUjVDbFB=QCSTUg1(QQ7^dgV};fWW;i&BX+yLB?%~8UJ0#MG-&Hr5E3DLN z6=y3WCCi4N^^<%7{0e6iO?ELhD~u(e>Ea7M007NPeG!|#T+I}^P4-4`|AaJd(`534 z78Ht|vxoQXlcW`76S$-Bj+{Vr$C~?$jr)j0vy-_c7T7;E48$WjZ#u`XaV6?MqaM+l zSygX1{$o}JRThYMC4FA%g#4w2W2zPb^bA|m0L5MuNO`i=#QeqY4t4$(aVb@Sl=Ei$ z>LHk}{v9>tmmzAdDDcvTBH=@Nhf>6`$_0!A2$O%c@Nwj&3diYqX%N0`Lf!sC+~oKC zi6@ghxli3pjr`7g#Z3*zh}o?${IrOa5q~11s9}XeQ_j7?qnIe2ErYp5*jy&t*FY{O z2Ai1wewWyoca&DF6wx4HTXj+#CE#=1d!e$acTJ-q3#cwwbmsT!+vj}rIQv&NFE!q& zT7fq1d|={F5aj)0ufFt1h5Q#5s@8sd{KT5u*@(Ug){)ENf_6W1OMb*L(60>o`%7k$ z=^QT)Y(ERxXMyJXoScR)FkPj*-;ymvfx%v@7hp5dCf+eRBiV1=taRRW`KzSkO7h+J z)|>5TS<(TvDi1g}Nf@LJSxV^3vHnTr-xST?%Sw;`#)uK_-`ePS>Wx)hfOYt(69|T2 z=m-4QP70!2a7Cn%39V(7AQU`l+Y2Fgu*F@b1Fxz5=5Vsi|3sxoab$!F$;V5;xU5mLK^L6(^7tL`xWke$ge zX_p1A!iYRpD7vBZlE^dL6V-M&+9~An0eGRq(Z+)Q!Wk6}%4~1mhKlRuP8z?v1Gq+^ zUBWl92N=&s-0{AG(ZFu)p~=>!%AE_{wBXQppRGJKZ}^WG8+w zR#%Q*sDNy|cyzX>4@>F(9mPN|(;{n-x52#b3$U=cV*7dsfnIgsk*@2qwGZS15fJDw)Jz5mFz-`S^An8|?5pe$ct<6vcuy=|Sf6_1)#<;Sy(cq^^|P6qF` zQ*AF$qCbbAG%Z_BP=xpM)=TL<_@vekx~-GxUk^BW602Srd<)F{?m#F{V4VR>pb6}c zn}s+98f(+o0bVNnv%`*#4^J*L&@Z&eTk@Q|eYF=Rql?Wu#W3{x%6)|Gm)OPXFR-Zd zx?L&<_&VQuDv^3R5smyfhaCU`+&2|`U@f)opq{qWZPdmah1jQLO|KI*94cj8s~^W# zxf6%?eh+>9L;5#$btqgOOR0PJSVAvhF3Cz=Q-$yDVuxMT($^;9!28H;J-6KKng=P% zC8Q_=9StG%ux5T)p_J$OyT@Q2!6&gpOH%L|ged?Yy(i0k+|cCubEP^(HHx*k$IUC* zR}IFmHGSV05}$=m&>D1LP@N72+-Ir;V}Bpt>yo zDLK9<#sqkyg^$cfsBb1dOvn;M}l<7gRZ3$q#U~Y)GX=mMDw8 zli}*ozdc*1Y^C2tw{?mdKS&mqA{k8!lsH_;4l{Oo`(Z1ohJi&3(1pFEv)hDE1y)O| zEL)Q)Y@dv9+W}bgB;WZjzMo?+vz;#e)}uzlG4&#liH@{Pe1xVrrG@6m^_AIVnaY^R za?+}N&m48qk{Sn~zSMR4ea{Ky_W0<<_=_>86<+^BEZ>Rm*CG=Xt;$BC9T+2r!}j_+ zM!BfNZA3`(O;T-TLKb(Iy2&pMHFy-#d*f$mo&FAlP!TgqkG<-M-MG8_od_KzGP1M< zKwYSgskC#blNsLr#0tPS$ z;MQ~m`ZC`<(J}ElwSu~+Myw*H9ovF;$2-Ei ziQHTq@9>&l1_o9Lz>7nuj`4sxSEZ(-uvMQ-Uw5S%GDfr=V`nl}vUOs)WcihLkx%2GKUiW-E4HtstVeEwlM z#rb4Y@Pp-{_23qs=}33ZOGav|%O!-wzOnlKP68C~R9S@RhayeD3hAVj(?;{O!x zfV=eS!|U)mTz-KD6}HDDVuwPg$C~w(n+0|Aoep(f_Po?qMao~7Dy?~ivK#n(Zt^HN zU!$b3QNQ?nxopdL6VanJ0rYl2dpR;-C4tiVv(#@S`a-u_vmWRC?_Q68%qZ8TYWBKu1hOldEa zx~bVYc^4w4q>7!tu8ZksRuB1aX<9YUe} z)YV6=`cmbK*^Z~8cl3LWB@zM5M{pTmrFX1~EYB`pf`C=r#iw+#WXTw-_cVuF&OH-o zAp9*3DB5aTkAOzQ>28lwKbHv#M-fz(G?eVI%lSitUM=v~gK$Y%tJ=r?`w$81N>&`3 zEI0-HC2#<8V1s*$Q*JtL6PU|K9C468Vdu(eWN z7!dDxF=0I&9QWyf&*S{WmqySj)W$N$4`~-f+DAxbhuy39+3c88SsRWd!mN-sG$^>V zH`7GhwXFGg)9?!^RLtTazqeqpubU>`|H)L@6eBDUK6l6ga3a zg{U6}Z@(mynqSz8^~zo3r~G+@rD%8ADad$F@d+hG?AruJ^@b~1g4Dx!O{y;)D|8h` z7oA53oX_`Oi`1U?ne9%W^Zrg+StZ@qC5t&T_CtYN>khDb-Bdcuk@IkI^$Ql#0`B(? zN@vU$U(_s4iR(@OJPM$LHu4}C%C{vqY0s4}!R$i(k^EcwmhO>fIY z?dE-#C+{F`Hox`_RJyg>(bWlW?G=p9SMs?4ULVEP097J|J0ZQR(-wlXT@y_b8s|># zg1fs4>+#Noi?sp_^$3#dXfjB+zC3IEZavS%byfG|ulue|5+%bd9Fw29>F>2pe4;w- z0xoMQ98jQ=!Key#LB)msRq7tJ``&_R(!^+CMTxrc5tw72MaG?L7+GjjX-k&e(D@qV z`o}^@FK?mx?#AdLJj2$V=MlxM+z>m%x1sv8?mtvPOcY&*rwxLHZ|uIO$W)K+O_gh zjeqDiSO&i?#=o$M6GH%2f%O=m5Zjb_#Xq$aIaa--%M0*=X%LO#=FVRZlZd)1j%L;t zyV{-6p8nq(9@X=+RG?JDB$V-Kx8hL=7zMG1wTpu1HyZixF6#4~xM|Fn=nIX<^+MdA zoIc6!t9`)!#cbVuAlAMAP6NkjydbMUzFLDsD$h+HkUr)w@DxPZ9DOa8uQre;I&v77 z!U?Zu`Z78}h#uo8rKf#~YgS|y=q9@doQ5}C&m20k-(N=5gKW)s{HtVmGo?+1S{dfw zXL%~pq6Yl(Hoa5tdo1k65vBMI|9jY5p^ZCUdoJmJ7aMvn za|GB9lTM=TWX9WDy+c@3_31vC7l8&6u` zk65HsC`bjQGl=w`$rg7+C5{Bck2Vd3M%^x)F^ZjM7iiy)HtVd!Nha7V7~XHZ0l{l+ zb4a52o8T4WxHk)7YSP(zeV{=Qv_+CUt$>iJK&lQ-ucAJ^Ci1ABxw8w(J{io3{@Z7J zY}w1%m_;p<0qD43DXT*U%{w1U+!LE;kfpcdMv=G`qVh|?#8Y^sy~()uL;NoqSWYdr zlk=#uL@naDndexk^@j=Op;IgUkvb}yizo$+JKL`JOk=u@T&#p^kdgxmAOG;|@w2uc zhewQD>O=NLN)pKP2}l1x$7H>cVm=PR~aU$pdZG12@RMiF4 zs;%PxdbqE1xz8yXaRZAjPJRXrgve3W#0T@(A*8+N2ybZRNqe%k zE+H{-dC;F)1o;!<;^-5aImDA~(TC_JKM8^8-L8%+mvWY?VB*vOP^0rc_YLf;J5)wj zE$$yM&0j>SBmjmO!l3A{)tr~u3#*_bjIe!_X;YG^0>)`A@W=k7sg`ftvSNC>=Yci% zg|~5U%wKMX>ENEPHsjjw=ZS)P^FYmHM(QdnY|h6JnpZyCr=)*5Qa~GgT?GZ`Ql6lB zF`hAd+b2Qm)xRF`MpuKndjiYwgPl*sJ!F(S{|uTFyo&r0g9%U2!atvMTVC$cBa$KB zUfr$Z9Je-_Ed5$CjhFMtyM0Uiy!Ib>6E`@7fTC@}9kp_R;rn~4I8XJayyC20`o|%^3cheC|B93F2>X4SinvnEhnq|J{w@4P?w&ao``Ocm=H{K;b2x8!=x$6@}`-aI-yr=-@)=3WG-H+=i}$!2}9Auj;KLIxP%^1IPPEbm>{0V%a~37(HMJd zp5YBJq@g%mzvi30$0!@Bzt}%~MB2$1#tZcOd8T2E-J=}UebDo_K@mvOTS z;pe>_8F&V!(L??tX_oK&J@^gC$RpzmaHlGq+?O6L znU*LRhw{m`fT){BVul^aEr>SZrN&P18~8xMA=aO*JiOUUxtQ%n-z2_O3;xRMdrcsV z^2rv6-%y{&CTEA71V?@wSUUd}g;XcC9yF^n{55t>k6t4R!%Nas2#Q zA6Ie-P?bud9ueob=GuQ~Xl8}|sDj8ty>sp5Ca_$~?G`!W8h@Ql;nAAvD{<;|>oF(J zRLmwJG`bQc`NIHo`9!5)(HSx6VA10FQ&(~jr;qmsxwZWWY7}_f+QH)))Ah2{v%3`z zNdVC2>x{o49l?L_M@OgxmI8C+XBJY%^a;FeOffe|1y;fxXe35DZ&#)+|4OWmDbCHX z1D+*`fu2BF$|u5XEXmrwZ1fZZ%P5AH@zq!%EMp>0f!>^amqu~8B88b{B(cj~`nRQ) zb(la6=rsvC%4@@motjc|e2u}6R*D^zwyPoqw^9(5(Fst3d)@mS7dr6;bzL6$TU~cv zNxZ%Y+WBx#FakUEn;FNZth&D;72i=DRMyklMP({byNDb$POuq*-uc4cWG36ngqO)E@7?3$(7tkI-?)W$Cedl+z^uXnTKk8W;a65N_ZoTb6-B__Q!o)He%Mx2hFkLP1Frt1h;INwKKg@Jc#B`<#LUc8B^y?h4k} z?T3QM(*wjwG6-~)u3t@Tkr68Y&g`>@HteUH z=cm%SEj$l0(Py95!UrVT^;JKIi43@{Vj0G~z=Z(Xk~Lj^+nq8B zWhS!qsHDu~QMvcJC=baCZDTCY?_SB!KdFy#6xQ3tSR0bzDRK3`05q*SM?4m= zrx4A+QgHw3&2CPs-ZyKayOBu_EDv`^#IbmH_AHy_@bp3}4FUq8-0#Jp{1+TexP7J^>gXvt4-acMB}}A7N-EtrRN_Pi`kBDQR3nC3`2kmhY8GYSK`|+b z3*Q6j@LT|sZoF)ChFLcR#?4hJsNi0;{*l@zG>BR<+CwjUD&F#zBusqn0kJY(z4})5 z+UOm%ouR&E^UOtIIIk|1R&S_%n&33NYU=pD+xcMg{kYra(U$mifP9^tMrIK%jH-D7 z-zqWP*q(bbkaE^21>n}v>_CZ(fg+%0<3C&NZjlHu5z#hbDwI`MAe-cr&2&<}t+7p? z4!}9FKXxb`|D$_qsmn|4QZw^oBStG3nQRAq!1+C%jbfQ;F_wNN0x2bO?qjqn1%9*I z>$X6dUqtK&WEair2L%2z0A#i~SI_?23s78t)=`?XSfuT}yq|~M#uD%lF@tl!XfB|N zpJd4(ilr3fhtlD{a*ImwuD;K|E8$q!a)PD^A}A`Ea9yv#vZ32&6lI?dJfCkHEHBFz zj45?q4}*CR^hnnA_u{hnuk4~KwXMfK?X&}a>@(3c*MGpvy#DM>ugytX-d!_y|9+h` z>reF=I#_%j^RE{d-*$<8ODo_&JCLn30he2YkW$9;!60AL%`6AN3mRA-?%ek*W;gj~ zAj|wC!O}e+I1niGt^g*%DgWS@dP+T}n~n53Bkl+iGm(MPZfzu{?YLTg{U9!zB^NxX z8n6$yu#nzF0L*V`4+?>(6dIwefQinDuJ{-~uD@n9ISdmitjpUW%_e^dH9i1hUAZVl;$BPRe_ zl@45*B^a5zU2ZL$M^KbnJ`C6YkGU4_z3oz@fdte*FGhPbAS7vo*{)+j;QeN)c_7 z??$Werh3*zb&WdIC;dGNR9e-*WRzSy=(j<(4;V3ucMu!F)gbX)vh?RYSC~38^;6Zh zG0Ljnkiswu!wqY$;oQ!{MsSI$XsI>$$2%5(;iW#{wkYkGlkX;2SJ*dDK^|@~(0s(g zpQKD#wPuNw#KMmT^Ut0puo7QL2U+YmMNQ zU$jjX>;%U&Fp%Q%)pMcsQ@P4_*GpqOq*?#evg;1_6z2h-A#2mT7ayQupQx-AfHQ)T zyZqI4OT^&=4RI@Q*NOAM4Bj?{Ejo4d#M)cq=Esi(YCwI6;@@it%v}8^s|d!onw-{9 zD4Dg=aB_DhQF~hcW*uGxa4?!>$acAWhJz(iJl$Fjsqv(J!l zk8PY260hnNPXZ%;c~6$jGYXjL5_Tj1AB+|9e7l1?(w2{Stwd$S*(s_$ra@t-jy||4 zJ}dQuxT|}%WUs8HN1GlQWBLX>Y3C&a?dL|f3tT5xBXYz`5lKpdx79)l97V5SyUt!j zF0?1YJqKQsjFB~ocg_;(Ch^xJ{-hH>Jco`N@UCSk^bj}*aV8rN_uu*~L;-H@>Ja`I zMesv%G&Bb%wyYm3Jy0Ed2Q2}L@~qs&2h8;t{u_j9WF|OYD5p|3w)pyJ3VHA`_G-aY z-*V*A9p=URxTS%1uxvUL5FMlq-Bj%!_I1?6`F5~uJ*D+6pjanXS5yO+VVH9@N#S8$ z{i5slEXIAaBit`4{5q*-TV(w#?ytQ*7Jh3ZB*=0dV7RS>Sbsjxmz3A!hY6km+GEQ- zI{Xu^tm++D@Ped6eH03fn+rxrHx0z|k4|Sq-1M8DiTlY|KZt=2C!YxZ($IdXXVp5twt`ks z^b(SAzhSl@TWmkH3M%;Mgr=e9d3E`>DvT{Y?HN96@g|J554Juo)7JuQ+3!y4&eTt? zLfEk3=O?5Jn~!VuLhLL!)d~h5{Vo6M3D4sir@8%XIRrDv5(Sf=s5`3gQrkiTgaxM0 z1k@0qN!;LIWR{fx(4nh1U1ZmGYDH^veMu9DSAS)lYdPUek>7fNU6o_{*ylt@ga=7b z`nUjaMviYHWYs|dU|%p!ft9ss%M1`uUK!t@NdnGReOs!eIR(jl9#Iy%x%`1+#n4YX zM~E`v#o(hm#~ysR%ZoSUd@T0l+LFFwshj8uF;C+hoVZPMkX#vHQm~uLhM^o`KziMx4*MLEg)#l`?t@Ngx zn*BqlZ@=-{4FsPA-UonjQ1gueqo*r6V6aQR{}^{x>7(ev-z7F0B5fwfSWG7{&k%Az z;oaQE#Y*QBUad9y+gNX0hbm{7cWO-M>T%{v{LO}@cIirI*a4$b3!{klJBB{Ii4EK5 zgNAm}eRiGm1fXtC#cDrXMzm)U>~MSKXq>|+nQs5hx2Gke2IE`(ovgwI^_)StA31!D zAH>J{*-)b<)0Oqxqm05aP{4;k zt2u`=^usNL*#w*K)rq0Qf05KP$NoWL#lmcxeNJvPp)aIxo6+gD%Y|?lCk+{vs1zkA zI2iiKs-<-dCKslfc5k=QkZrCxG=lbpbu9oPirvF~Htln>sufty1(<1CgeX1c9_B-l zBaA@2oyMQoBdYdps2W+JSPT*Ur+*RHaA=i zXc(fb?T(GRRa2Ib?SruUc|eUSxS?(1Kw4PfX(>=8-Yjex{VRvP+mrCFQpq5sNY#*v z`s!T(VWHQwGAwmm7Z#xi*dNiOIL3kMN<;?9$TmqZNSf3-7Y;e??H;jFftgv>^UqJ$ z4_mm?R()<>AB;O8T_>#P0Q+caMEKZqIR%V8sbs~ew`Y6(shdb5mjKfhxc6R5Sv>HB zB1h|YnP$~cDEaaxy!FN8=`jOG^b!-fau^TXuA!$f`Yz4Vj|Q`a{c;*lPe53SiM*-; zf9u7g=l1vYz;UGmpTkn{0Ik=d7IbCiJuiZ@Ikm2Ji;VzNPv|a5Wf<{Xw)4AmIC^nE zs1$xF-kVE3`+5C^C~|~$uwCMBaRPHAsK+bQy}mRV0|12!A0YW(+vivhLr{x@{Xp+E z-Vj_qvO6wa9%kI8RgRPUaPjDS*IG99G$;R!zR*XRzYjWe5^^`g^!)PYf%DNZGP`q* z=lHer-T)rl$bI?c&jBV{h{Ckk45I09aq)Leu6#!l5%2jJSaUGi^7eMjkmGm_Fp4j0bfpA_a34!&<7t@s;A zIGUi~4cOF;?2~7*VFTNE6mIFeQZd+~$#kozL(Q(&F0+OBy(!S}|^ zXgY&a1#nq(p$~A{00QhUb_AJ7;U{_<#dC`;qO&KaV6Al{$KTlA&6EbfNRBW@-58`+ zcg#ug9gjbmU=B?csN*d`J-tC2OF#f^CN$X~ZfB5#-b|*iKHOq6u>xobmv=SBUxbTe zFaPq$i{GF7iEaEa>i%hh30v_qtXW|pM*;mPxHunL3%;hJrY8KxEK!i=X+X|+U`RVZ zpZAc}Yd~tMJiyZ*G6yvF@cj_A((=^h4x`Ct!{b)Wi1KHVhz z{=UN9I?IMk*b~v-AAR(*9QFh+e*f680-w-GcYhB+$m zJsb1mLwE$j;-KU1#|Kz_#YriQSh(e-i5ht+HKxLPc8&PG}ih}}|wzejV z=Yx@PSoLp!nuq}Ex3fAT?=Rl{>;)j2cB%|${6wo^q2fPsy&cvqeca&MKy2HGIp{+Q z;JCS^u=g#n$)iK2%kJdL?!nKoTW+KC9D`qw4mY=dU*T5xWL;4b^ry-07czNexH4-j zxS!I%wulYCHNNWc<5)I}+$@3WR}R3I`1Y?)os;>5yrDsbm!}XEJB|SA0hi_ocZmNY zfZ*u>Q<U*6+|rO-F#rQ#sEM;hxWkL44^Q^oK%IXlqp502dD`IC{WqTuI+=lk zUa3}gjnzPF1DrNeM1)akj6Be$Bf6TdU4q>Ah)BTV-Xh4#%GsaNPUT2evJyzzeec-mu5CFvAwYXrW^=fqy9!Q@q^x`mmB}eX?LvqP4tsin#1Z-~xfdQn{@i}?w?FPg ziZXp2nnf~VlD-G%M=?F#m&OPQBa5ZFXhx!YZ~xrN)#H@x z{4l-R&$SvNiBXhE0!6cdAd$cK0oXNaYPWIZKj8c_1>;>+g4T+<%DTStRk^21cLqDF zlY<8PoSzN{iRYbH=fCUf{p$fY51#IeYhMZF`5QOXol0gByy8(pAi##4t1hFz!3@YE z6fHGT`ddhJ4~v6zs7$dspVuL<#vC#SW7=9(+eDy6yc~ZW&6xItmJ zHgqmQ9hpoeS@^Y;eO2#eeiM9cskKCaesk2Q>!wfV{VUIvF;52Nt0`&olG z{BHNo@xJ-o6*3468DL63MyokcRGQS^R^Ezy>JImV*AV(5e9WoDN3j;pSS% z!sIZ2Zbga{VbdL1df*5Mi0ToKcE#}F{kPXvWvm*c)DpO_M7+%*>uOZWCS!LXI<@)O zS!BcP1Ys>65F;O z#D>BK+YJ%?gd1xjb#u8-$`NK*F&m~@LyClb<~t8{lOPXj@IHEEga=bMWT7r9Xs5q; zaviZTb8YazZiNvY*mBviYf_j2K~|OfBU77%)jFw8q?{O<-Knepbl-#Aplj~;Z5;-3 zgF^>`NR&Oh-82_mpPpb7RoiGfUB89f?AV|!c!=kUJ_`mTAy`7UVG~GMgXSbJBmyJF zg<82b8oz6sfqP$?zG%HF&;r@$v;a*i0cErk=m5Y;Pj1Rd&stLEV=8}tL?&w$lPXJX z*K=zE|3^7sm(sf%V2mK}!AA$4{Z1~p>@OffiSM0_9Fcnv+;MMw6}$rIth3qG#Hi&Q zkHc{%@$+3HPJRpVcEAc~sK7W&LR157joiyLxGZ z`I2qdQ9xNF#1+AL*uNyxIK73O3v!Og zUNI8j;|W@^BP=A&4zDmniZ3WF0VH2wdN^Ov1Wm%nqH~tTfA#DT{$;FwN4Z4p3-)n7 zJZRHj&JHQF&q}m`Qy?^udnG>X$E=hyCMsqTP3rR%6>K|22Xlf59&ILy+woubWaWt# zBAh+x2so;3%Us2ewOQ{e{Wq1jA&xVJ!j+D-@CntK6@fS9BrnmL{6_l|03j zMf$J)dNig_J-^Hy@4?hy8PUy@1sN}|yA;m!;P;1@(ptGHD+EVsE~iLxJV{< z8(Ese;ea416^6|re(HIo0%Jjh+)tRpglY@)KB!TJBcED_62jTWwNl2(>hDs}JX;r7 z7Dm0_-MGeP)duBCme-#==f!yZ#O!wWuM=cy`8E!jkl)F7a#f#77t^c*>rLM&74`Y2 zuZ803t6<4SjmDq@+s_&Pa(B~{#F(>*VyvWdZop}Hv1dW`S*9NO0vDA>V=Vn%%-X@p zVH@wCc&2$$z1#kc*y6tDy@gH{+oBenw9w=Xex%R&TY+Ss+1~g*-b6oXUIq!Yv3|>i z$?0%p$%r+@T->b8+YENJA*W_rQ9N!=PZ^!3*()79d}Xfv0QD9qNIaY2Q$x~Qjzw?t z20PGv>&T-cpskR@aS8?FNFrYd(wgZBYHSGKx3vcjVYH6&PWp29WD6v6nSy)zRn6xQ zcWUUF(hz6c0wv*vaGsD127BvpUE#&b{5My=-uX_wtXmwSGcw5V z9Tf2{X|Mj9tr(;~9$4CekqWSImJ9lc$yim-w zv8*m3lgCtaVN6JU5s+*6q_w06WX{F)BhL471@#W~1&R5C$Z?4_@O8bPe`lI|G5X7$ zq!FD9(sx(U@C*aXpetE~snohS9Vgv%1JGPsB6S4h(|5X?$*kAlSP1$^!}|_o5MBgx zs%)O4_|~H9E|;=|w}9Vr)^DXkK?Ub}TgVc(gtec4>sWE6@&bpAw9ucvL5h12o%PN6 zkRr$D;@>y`Tf~0+S)F%xOHfB1I+U0Q1`;octBF*q3A=({QSkN8w;(?%i&udy9T-2r z!J;zVhm8ustuS*aNLw*VXMA3c-q4bZuzf?~&0kV={>Z$_xpHQ9!ChDJb`+2edDj+@ ziDQ}o(=-}4w!|IKP`Y9;;cs~S$yoM>K&CMh_IdjE_S)FlALw-Neup}R_X#-Z!ye#P z(DVn2xf7YU$ya|&_4Y{^{S?+I?VTfllrdk?h-_WSl2VCXwZEo-Z&JyRKQHPH7&RRU z5mmRM_1#D{U@Sg*F6kb)5hQw&)0O0`G1T(=qv!w*&8qY}NZcmLiqZFC^xRFiF{`iW z{A`>py)V?ldHN#nbS(8h4w#~MIJOr*fJ$2E>cJ9-M$@Zsie`M((qq8QOYZ(R)%h!B z?-f^)_K4c-bA)pPYgONz1#&Z6?R*EXfCAZ^Ogo4Ci^ZRnZp!ibh_APn0OnWSo7mE7 z>$Qr#(_x@?BT#8^KBt-~vV6z# z2Apz8LZT?LuqJ66Gh!x6LDh)eqnUUMppy-qmbZZmBL-aQxU&Mso?y<6 z`GeG}*`~a4Gib!myUC|4(zqx?#Ug2rRTIytxgN@Q3d?`ihMFNZeLm$Z{5N!JzYLSFQLG2zAc+;4;$(HDEpJ?ppfcm z%8Z42Q-HbWuLord{4ad0gCjybW9O`=&!GQbH}3CBwtgOAwXHxyaUzQ9s$jf#cCr3= zN(6_jAq(r#g!!dk`+YKTUTXgtcU)1qLr`GtzK<2DE$L~H?IX|VbwD#jL@e=n4YL)} zdA}0$we17kRAm1ANP85Qs9zQ`U(gPBU@N8%(24p*eZu~j3=i|K9NYldrupeMZxYzXM9GBXEr4PUl;ykou;vpeLl3b0mf zMWEJX#%|-dB0k6zcUpWa%SS=yqi~#n@Qf*C-^mRqqOfJ`2ho;UoI2dW`^eqp>%h&ne!Kprd{d+DZ4dyap#|+89Tvi=Cy1@N7x9SBL|BGao}n`;W!*5XMvxXq(jHZ zf_ipaU-Cspn`DQcp54K=*?X@&@v)q1PA>yV=RYUg-{I!2RznF&Gfv9kef}7 zQ{{UdyfTkOBr@F|f2EX)lgzI9xVh~$c@;Bp$lOuPHzz*Bf9{dWvz+=sDz8WXLDu%F zmc|ZKTuK7#v1dD-3hKD*+EY&3!Nhh8UzogTa5U-l?;og3dTzPeslkg6xK8&T{GUhk zbXwh@>Yj+svg^6w>v=hIN^N%lSUrSvzlY{xFW6co5=Hy_y?376Lvk`8shNnB1%$KAB97-PfLXapX_{GC>$9 zSwCkGoHo5RB6zH9OEX<;|HrVwWlUY5*P&4_1Xqurntqy+^CCz*gWSO4e6vIP$v%3B z=WwP)8@lPUDH?dI=}Gv)&QU1_<441mTeKk$!3k%;h!Hfo+Kda5m{GA%v43~)du|Uf zOXFz5r!kj6tC`1#`Ri-c6?Q?jd4{;@#oW>r zLC$>I=pd-j>p?Vx_Ed%Mdr;6VR-(7`LeN(d!mkKe<^7vZ<(I!l)ZB;pw}=%pAfQ(n z$u&D(H||kEh><_|&dPE2_sdbV9OdvVF86AIf4eG$~#4#BBi-2bq&^ttq2QAf^FCaa}0!!Gcm-*WJa{UB@H}Gxn z8H4lYt~ac%rJi3T?z;Som9f_lO6ekrD6+ZM=C89uqDU_{kWRm&Tj|iRJ>pxdqZg}u(<)Sb|hg(GKLiXT`l`hzE23w#65JE%6lrqokV{0KjtI+J38-)k4d zKZ|B71#3t1!M@4QIqLy@nqIs1pEx?U4T_8ukHL%J>)~;7CH#2oHq{0Jit5-eIMsaf zP<{5o%~a>j7^9t!seS+iGQdM$9`Uru^ZKiA`8tm3CO$eTm1&k{1elXe5XYP(ku^lk zU%Kc@;fJpP6oI%kT=O`Bn&d#JF585S>&@4Bp2f5!=ye7>u9@G-WcU!;{HO986sqBc zC313oLTa5>z3^AfF2@*u|SblF?j4+(jJ9=66E_h@H z`Q7iC@|JWJb2*A0PnnsiFRX=r18*L=zo95c_pst3;8ldkF~jH8ch$e__+0uv9Puf{ zKYM8rvGli4&w$ON{_>LWsjT^_!*h;ytm;@aTgF!y62ouw=(wzg)${K+cc7J4IO5_u z{w?SWL&%qfw-zCmEdSybZQg&769)%S7tnW+*KaqwWZTfTtuRLEZq1BV`>CCIzc#A! zypY;I7a)O4%eN7%I6YUjjTfkt8DIc9iwHTMb&V<{zsc-YTp&#gSU81be=j{le6Kk+ zl5Lq~5K8pJ*o@^>wqCXb9&FNodwl@9BH;hu3^iOmKehP-gH(M-okou0Lo$c6GVN1*S*1pf5^(NgXP|-xTbC zI*@@#1&A4?3dmy&nu*Fk2u#;MSv>)fe)~51#mcm!KXYyS2i-lc$yZ~ zb*6QEY30}$m{%h*UuK4&sZ!Sj5Kn5WB)x z+qW|dUesmdoLa9#PAiAML5fBX3f-qx#BcsOsD6;wYkG`TiAm^PL_}ByMOubNSfIdj zP3(lL(b0%io}2Hq-(3*qb)8KvKi2&4 z5Y+?#%7|WLKW0|KBTt1c4?0KP_if_y=gT0Bs#*&KkZSE5bkF}P^cpO-OEEWc{k{r- z=F9zO8#&>BmI9|%I(+9&7&@XiUQ+K=JZHR>Wd>+JrA*}gxWkelOGYXMmJlk7!J+g6 z8nXzY+-T9RoLsIWOhO7s&!=vxC;R>~IE5QM6{{&JHNfIo>db*dm+tmOf!D!BFZs|* z#V9_-bm2zVwt^_0u1ECEwW>?5Wp??t>fETd&5JaK=)n=m^%iKv>Bn}av) z1}{9pDG71p5%aEBg9XWgw*^4+=g~>pSCgOd4QF7+7Cua<;JHN5qHIa7^F5qq8$ec1 z9~vgV>D!pkZ@OIo0+vgmu*r|w$5UMa3)>`xa6LLly8bMDlEF(C>NRME^VvBaH(%i$ zBWl}cq9Z6Aq5nDVHz7i7pd>V2c`A@SBot}|<9sAW>`<5YXMZi&cX$H}r79aH<~$Wc z9V*Q}ut8q+@&vq!M}K&SOaf`+kY)+SilYWXIu{g`vGLC^eoAr}F24yqn(S`3>*n10 zLgiJrbhJE?8Iv2Hs^9#*5y%~zx4`pXwG{AawUW|&aX3cY_1!M0S6S0X8k-9Qjo0oo zRGnLQ$m3k;8V6@+6j&d1ir!jGHbs`56R11~Ml&1s5bLnpd>M*iBR+>Fb0~Ok#A(s-pEs!!v#w|EOUHz$H_FeTE5Wfq#FRg=vYe`eQJy&P*3VRL#&yUIN zas~{qw6Qp>%IxegOFZ@ZcwR@ekI?K~%>19!#_)6T9MA2wbsO^<7z32>HMHz^qOSS1Qw4{Y7G@663$_VqQl?a%#J&; z{zk$(PJGlW-@U|SeUWM#loyLtvP^suMb-K;C7;+OwsVh(4HTtqu>&7%zQJS9GmE?6 z`CKg$1ush;x*|D;)BkG){#j`7rA14e`88sn^QJxO%5)A{c8Fi}1*70`gz8=^`G^ZI z^E2qpHR7edx=h*{oO|(5(u{g^C_^5f53dzNg~y4ZHx5yZvS3=ZunN^#-{E4o)h;zs~oFg|H8)OOF=cg6C+`7bF_ z?~mR9%kl&LL+Aj~vWiFEOty2GR)(6z8yjpeeMO)bci-)ifEb&e_m|G@X)oW6H}aUe zEa6XLfxY#BtK%?+l`!^X;oi?&;HL)Oci+rZXZ^iet7PP5S{57sQL`rIl4&zlu&<=< z*8tupCIwxOYjCBF7)Z+enEukB3!nQzdC%Wf=gQbF z&&?CI&d(m6^)b9zX zQ+P|!Z&Yl{$Umc>(L#}`R8!$z?lRk%ev3pI0!F&)QbZcLo0eLMf+8AYO_98+=3zjO zDD3a9-FPFYU&%dG{gjC{z0# zL?v&sz5ru}Kk+TDH2dB6Y+cctirPmqZ)P*JF($S#&imZ^Sp=rrMo^X542o?PB`h1H z!?x^xw6??v+cY6@4#`&{xV4^?&6?VK$?~}F!pUi)wC!0^m@j-`t*r4J0WSxGb*G5Z z_aX5TMd{=V9^KsYzXnT0CtpBmu!6Hr&dDVHq=6_?U5ve6W^Svs4C%?WeP}70{vr40 z?DMeVNfMEk;y4F(X5=1E_;CsLp_@9Y3hy-JksMe{QQ>C$mwukOG+)m7QDKShm6Ef2 ze}>(SPy4ZiRJRK9D*1xQE*IhT^VvDB=y`|j4ob``4j3uL!VYWPan+hr1qNA~XV)}A zjv#d0ydp7V`&~Bik{I7m=rDA|XstZ18Y>_RZ{tArFpxCSOfDDqhFIRj0R~6G4tYQr zsuGsC61BEA`qQp{AA7Wr0@&c1nJ0yU)Ccl4^vN!t0DF839R6t2D{_>kg6hqs#i6c* z<4h~*gPKrA{IYxWa*9UjJOO_L#%QI8H}xTjb?g%d(rvM`!rftZ;A3_s7YVPvDRbXA ztSBP7=W0*z$Sul7zSyrtk6>6JJ{~^G)BuQ#7r~!OTv``I?(5Mq(u-J#vW>w%hXV{Z zU^uU5a!s!GqtUEqNyY3?7j5LNy52S)ODAfzQPplzFyQk$o*xPmj?5(^u)zYHG zoJL0VbSAgE3feLXfL%UT&@$wXC1ZhmZ@t-DMO%u}&Gu}K zyO{d+;lut%kRuiKJRNx}I4%^!IXTTrVE!wUb}kl(@)n5nh8h{o8diE-;@7^m*}=hx z9ilhz{bXfzB~me{xccb#;Rjk!U}MVeM(9MYTXG^x&|uJPbE;ZV3y^H{1*#vCSPnfR3mAAo3gS^gZBSQx_u+D~Cc8 zF0LD%#Qb0%^Y!n(d6thkQ>q54`R<5+Q!@HX#94Q)=e<6As{BilY}fhTyqAE+HZ9Ie z1Tmp|8?S(kC+$Y!HK#B1x(bge=^a;tE@ti|Yyr2TGFt=PnU&lYhZq}!V4$F??n!@` z+hn9}5T$IO^y3?p?rP@^iqGHdyb-Q=>yz1^mjhj4`%(mV02og7QG(KhFu*+@0Jfi` zA$}nN)L41=<_Fa_NRw8y7gXO|^^6u!?$!;N_$8T9C{yFxdK#*sIhm56hN2>Z%lxCL zgrmE650EOhwB!9v92(pjbM&u2vb~53IW1%rx&W%%<$cEe9AF%bRWt{l(VYvL3T|h& zreH5z_xLG<50rrU>Why-OC@b$EBP(ArP8xqs=PNC=noBX-276lboKgdZj)6*u8yGx zxL8y;9?zq`gkw@45`SQac8Dd+S?q%;vQ*A5L!Z8Nd|=;KLW%nPlsejTZ7Pn*l*q2NSve`Zz-|HA)j~cVb%O2uTGRs~;Q|cI z&%26W(f+4t9F5zB??+oyE_G*xdrJ0xHYnzi9}Zlw9zNmXd#U$?aL!7Oytd99n+;0* z3v2DwlhtO1|6`FUO$^_ zc0n(n2;DN(Ji%zuLS9?0Ic?+Aj>G7)jp=ZTLGv;dh0-We*P}qvL&*x>&juy-krZ^C zF?di|;yz_nE>#N3t7UKGrz}Q98g1+-hN%1-h!5V|cokYL{ z7DptoV-|xxeu_bAy`6yeUuBzq^wvF1&96rR<@;&}f`!McwZC3+HWJ2$AH2n+E*`D^ z@#NG5)EEn);2>`#g!OY(9UC|H+cVb*`gnW@AjKLF_AOlqxJrw=N2y$I{r*OYjeQ}o zpHuZP+9^)LyZFO6csCnIpKrz|(nt|3kp=rCicXp^f*m?>C8uL3TbEx9s7y^M5eLEpk z9$v~bpe2ofx5SJ<eZ2OedHgu*bYq7l$y_I^oc*WTm5(m5j1MG3L{`d|lLI%VdqS4B1 zN9xn{g?=p{+laR@f<6?6g%&S7C3P>EjDLMG)pa}hY{u7#2|SJ)y&;w=1wFXP-rDr$ za+3!&aU43;c9h=_%;75Lw0x|Y^65V&M=Ukdlp>o&jvn2(zL5VJZ|*ZBJyP~=P)JDJ zbg1-x0bJS8Ey%LcORf+{L}5Nr5Kx!y0zb43dX6OO7(sBzI;yxc7%25Y`A8u?mgPHfb@HA=Udn z;O%GxpIG_x@wPwwe(vvrs^T&=)&zGU`@g)upAP^J&&3I&arfc4DV{{^zz*$!|LMD6 z2dbQ<2w=2qBH;(0A^8+I)I|6Lw^OVHEqP6ArRc6{&|dQq)gR-|NMHk3@bR2WdE}l7 zK@6Bm3IkzLq|Yc{xol0)ixFDq5gd9?U(80a58CxGk1&orhvo*+ph|TvtQjNt#qY_k z)iE^g_)ExJR!Ud6VQZ6N?7yIf87@B9^(c()TB;73(9z)8U z#NKgm@WrIK-`Gx9jh_G^dpJ$tnNznCKTp1g^7gC#>de@!>Vp!$om(aLeEgNm4{2&| zy8IxafJ352c5+g)Fad>@uCZ9Qhcqk8`z1=f^4Sm0%UzM;IZHWZnDvI6AV>XX)bBy`rewlwGIR?S>Pu{FsT@A6-r z_)AYLPWPkVj*!Dc`?6)7lEA6J8DT5|e-?gSqkzHpF|m%KFFK*vZZIzT43_lsRQJ*T z?~&reI>~8d(fr0Ccy*s59|1Y+P{3O-97Y?0-!)xa5rzzp3CjQI+gh5v6L{`yh2p~K zHTuuc-_;sR3i~n91xxJ-giisioHLFH2Q3~I)Rvonqul&0r)r#86M3#t1p_NVv_Z_K z4)-=&C1ocs588@oo6{@w!hAVLLaF<>`OsqU4WY1%p413f zSwrk|EElUvE3;t;^FhwOvad5o^S;1x|}VB6w_i1hhF*UrziIW zg=bK1r^a!F!$SmqwPv#7aj-dtOx>m3K1TEcr2yzA$Dkz0K!9&U;wL2@$AT^qP=q5< zC55Qkl>Ri=WB#xGKg<0Wj!8(N0w2*cl3`PT9w3w#r%vMoPs6izW1C$~pcY+P%jTA# z+Mx97Q-c@$B*vBSIBIl>OHx$vDL^SR?m&t3aobBsq5|V(8L6=;vha{we^N^DfkMbb z;@cL7KPpy2^Ke(K&UwgO1YfU2tu*;9m9k-LC5ewwFWz+UOWZ25VvS=@xFe?NXw!88}LLFu2q`{+qdlZ>oFw??3Wei4NKcyl@I?G{<2J0TYH>DOh*>USvhZr3Gwh}fnvC`BntDG7T&iFyJ8_eTIpGM6+ATpKAVpE zpM1omh#t){H!z%I^cZ-mMblxV5==8y?r4`_UJ`?&ZkbO2q$V;AdM@;sOB|5dN~a2N z-~uAyy-UxJ>m1R8uzwYFtX1F+o1m?GEG=e1$*R=rh65p#1ykOXcjw^GwRSEPA9G)t z@`)_eUfhSm`>UwgHQKI8c5L40Ou5$tSP)h@uJtufl31ROku?Tt6;Dhxum{)A@BzxyF z8gx1H7}z4hxV6$_Dp4ZOl0*%>O66si2%)~1Ts(Ln$jZQPJMoa}!o?GX8&CNz#q6DN z{F+GzIZ^qc+3VO8Rai;`e^NK_fyZGjST&yHru?@TAm$r1W^fp}swKV*rWYZyd?`{f z%z`FG-#wI@O%3R}yio4-Iss}k4^yO(g^LRYoe{+lJA$pyXxu~yjN)RSlLZ`P$Y*4S8mBHMK9KFu{;-jQ_^aR-q%;$MWkw=NYXxZrYiNWfAM64 z1lnC$YcWr69U5~4S;$bqQvIJ)28B81e~}6`S2S?K>CYm=Ro7t|e+2j<#1&lW!3`&S z_EwVn*@$=)Mv(*7!S{DJ1qU(WZVOxpyQHu#2B3YY<=BRPWCgGJ0}CvvITo%&Arici z%}^gE2%A9`k4r_FF#EZhWvIR8LHGou`_J!(_H@2?hK4CTm}72c>of*X&`rQ=M*;WG zioJM;usj+NyM5%tGYs@yQ!fLfM2qg1AbKqWOOIAFhw{>p12~}B;EJAPaJbWJmb+Mg zzB)5Q1u{Z^Jo2Nlu$&qa7!%>73Q{CAV@Qx@J2i2yBAKS+@ZxaAH&C`4l#FPK-HeNS zA&va}CeKx3QjRcBAve>r&AwD$NAVGscvWAb@1M1F{s~_x5fHhfQ zOSm>)oLQFLEA>?mP~hR5s$ozZ87Rj&dml2rxVf{iD~m4pK0#p%kUcagBgTRys1yA2 zsZc=h(dBsN-AMs#BRYN=)YLS~wl{!0Ppo-MG)x&SbGYmNVBouDMtf+Es_aZIS=}#; zKL@R&vQ@n}TfRj|Sw4N20?Of{j~dH79R$(yHsgz!J_FEx3xRxf zD&Bz&CG%JQIt`q42)B^HK7|06v-m@3lf|L3ib{@w_TS~M^Kp^lNBe*G>=pB`>UjcN zIQ0uWgQ~!|ok5DzAV=9ZQ9e>jjZtJ9qr6kTHv_ieaIg$hp=;`8mUV13SjR>W_Dhg% z>8l|2#@+DN-Tuuu@>Y=!i()ZE5;2ZPFa-h&&0MT_N{j|A>izgqZnrhpEY#hq6c8oW zC$*~an9duQ=ErzhjFP>fGpHH3L{`C6u&|mEg2x9LM5s-~kan(uam)Yj6a~2f=FPeH z`zLI%B~s7D_sf4eB%ZrqOpk#?>uQ#m1mF@e2nDYM8JV9DT_|KUQ29l}^cA4B-+*>? zHh-e#Bxh@Cr_&5pZGYB16S!N4+X5E^eCi9Pz{MjO(|Cm&HL4g7C=hP|x5>F^Be&QH zUfpA?9|E6^%N?(Rs!q<788G7uC%Q2Ed5dFFiwZaZ=oPToO?2Q7Pj2eWP1vM6Gs8AlLXK!NzHLhXK)S$3 zsIlDhv(?$!S!}-sJ_=5f{XS&;@&J6*7A!nJDre8-h_aagTO{DQYE}kptc+ie^mI_M zMhA%uqrpBVzJKc`lCw-PYE*f-zM+vl*{#Z1?lh@X`13k&eEsFFHcIEuzpLcJ5W5#o zZ^IUwK<4)vm-A70k;(2X)A!j^pt*~B*D6Qy&~q^xxG5WRe553)V)VM7n~SrWKrfDq z{9Di-AiVp%ewR@4UE*I;H~)-$ubTW89lwhzmoR}L%Zm0XK8*Ex?znOUr&mAP+FxM) zO)2ko)dVznc?IIt_@?E|Cj9q+421-E%nKgud5wlt@Az<1m>dp|NeA4x07qioNZk=U z<_9Z21Bc?j>HcvEI+6?V`a*P$f;2uO0A=*zXWwg5#XyP6tfnUzeJ8-Q5e8yQ3>W3m zSATe$YEEJ#@%h;;UUshwfnAS=W2gykaeH=h<}+a=GYJ$wEkuP(H~=b|!Qco7riR=D z$<>~*lTe|T!v#%nSb0J&hIDpL{2h3O0}TOYAiF(F??Z}pIt||Kx{YHLZaT7xo~-C; z`KYd-Pg~5T&n_of2?=h>UU{~SVy~-tv_Iba-V|{}0(_d9A=R%d6fGm|cW>7p00|({ zaR)BU9)Ww^jxxhtU4|M3km z(eaH0G3VxcV+;L-TF3lfFf|NLOiJM$aix)xtr8oCv!m733D6jMXj-3!2efxb#lXXK}3snF$4 z_vpAkK}sg4pkHLA(Q`VWQuV=wIp(yfrSKU!x z>Z~oye3Yn+o*>C4jMzf63~44S7HbhXVbtfkJKvqf4oEx<2-yYA&X^r7kw^p1{V-R)I5aB!WrnZvy9z3LP7UGO^O_<;UUyK$}(#`QmuUDI}zbflE7??k%pnz{47sL3s}n@>3$O zFH}7rH_ji=!(>jbK_$XTbhr4v2vZGZa(@Cp4%Pp*u?#rsQh?ln&(AWFW7&v=Sqw?| zm8gM7tbqAjIH)>H97eT^9aWEK+S$%{V^!iyE|k-g0T~<4L-z-W&9?z4p2HI@93MV~ zZrmP*)Ko>W8q7eNFcWzoNM!nLbB{%B85Y~da!+T(?tR3*HxRgVf{p@BIH4_e4uYsF zf>i&;mk1i(ZPVLc&F0UTTbS4Qc<*N6yS^%KcMW-IYS+8>FYJn)h`Y3#{Pav1kob*o ztTaDe@Oj?OC#5SbRWrXi&vO7pGUhy92(LO6FWc8ZKdX~->nu#a0L_&@BkQXzX^k`} zC&g%IX%t4|ER6NdZygRe8OYLV7Ui5jKi?R1xIS(>qYHaMDKBb zXQkd_l!UFv2T^iqtOIj-0y$>Uko)Glh-a;c5nuuoAyEl}czrc1oa$_*^&!Ho2Z%C$ zE%R7e&gzc?l`js@{+$*E^21TNIS}hmsw9!2tVo6$Y52Z7TRA#vfD(E~YA8Y=UDgMSAfjxV6m$ zUR3aofP$vaHxxuk#c)0p3IOCS+TPd^OVb#`WuJDGV~3j8ZoOMekGBs48KYh(P3q}^ znCYP=FvH4NG{?|n!j2sqwJ!=lVZ-DBlz56**5PrGd64|p?JX9d5?LRvNh6dCWRdfR zJuX}qx95qo`g)%p!@z$#!Aa$P%+FDtqF^etE)FR;d(qwl+xzsvU8a6Y6xbz?x2)#{ z3-=p`lGC6hJEsTWgY+Q$27DJ-r}{g83N6zxl9#m!u@NXhia91TLB(Dxtly!tFRBB? zqGDCBsC>J~>J?*UtD>u+?WR-Zvy0-QwdrcF#hqZRp!cS+{hy3*=hRK-I!Y8zdUN9S z(d8rMOyocX{c1eNit^t?@OqvdBO_J{zAtH$bA_`_Go<`1ksYH3#&Ks&*qxU1Es6QA z^E#lKK_yv%*YA64fBBp{$GfjG7XF15jy|YG&!_1eC~76F zxUEtQ9AH7bpk}w|Hk4-+-)|-PNgkcm*Ww4vQb%(ah;U$0Zv+ zM&+C_d3{oeJbgML_i8`DYZaqaI4sz(5k6i|7LVkP=e2%{8mxr=0jjgAi?Iu_N6DXrU5xapK?5NGBL8!4r zdOjGx1q7*I(`v<`VI@x39rO4*dz1J2J$DvVjVPr^o*xs(?;O&_=@pngJHjlkecR*5 zp$?|0-1achRHtNGnq~WGF5^#?E_25b}RvcO2cVK-oxEF27poL&oN(dQxW?xN6?oS4OM%xnQ}{9eM>JHAa^d5-A58((?Q5JwuT7Xv{KS>`WYsC`Y$%rCV9wz97L=*`TIwC$8yg6 zt=&3QefR18ynlw3P!(J0BlPYrJOyDRwtzusCJ)5nT)Z+aEViy8F~f5Nl%cM#FBWj} zdE`1fiFTfe1Tgc$~WB+-6)LYO? zBUXE5@C6nQMfNoLQYi1#uJ~q7$|4^ThmrG;6oy)DA!-DC*Gs`+VEZ9Wq?;QBh%>$k zHYKZIp=c?1brx9EytkxKn0{d3LVeQ{AS(fGX7cp1&XG*bv>-#M&F8#Q&i6e1$Krq- zGasyHJ#zU2OTej|ier#lSU%|a9PfMPK|g{}A?J;TGK$(}qdms@>)!8`ojBne*75&w zjkUL;dt(PcGC)wRfJqH13w#)@+B8vJfhboOq$5{M6!>&PfoyNAdq!st$~q3MpRl!P zm;)d!%l~G*(RiX*Yo!kc zhRgy!>;H`du$%>)iOe@$i-mTLUqUZ;% zDsy9OQ+`_GAsl8pIMlv=2SVjugHW;{I2nv5LA0EhCILTsEFBmc0gq)9L}+X7b8My* z{ZMkZrYFZh6+wjgu=XSHNk@bW*q%M*%eHdnI1N`ZS$wGm4n#9uB{rf@=2*~rA$#x! z3x;%Xh4DWw-@umB%W_yN5AUbPm?yU9^P(YGrX>i?H-E8c*FPO7elIGSlf1jrUy%Vb zMDLe#HF-_3a0kfnGxgPhWz(FA%|EsIRQHL>|(5xC$!n4GI;>wo@=I48;HZ!(!w^GX5|W!aE_ zmDczAh0P*i7jX_a)U`9113Q?)|8Vluc^xZNL$0zrhghKWTZX}y87zHQdi3*HOhC)Y zT&)Z#L^QHn#m*!*S5hq3_V`urtxUaY4|{;*XSb`o8o`a`peETJ=vVLeEs9U>yymnY z&}CDPnw&vS$DtmpD4fzyZB&8!;}&h>og`s%{R{hsMv;!9?FWl< zR1&WlEhwvpmM+Z2=H3>z=~0<#eH|a@xL|wnMoFT<-_Wq-V)ti&hNpUiyTlfhs?C2R znk%%pZGLAT@faoQKLNLPvO&s0%g1Lu9q{_f{Q}G4uRp6Sf#l)>C!_G$v(;Nur<|Wb zW*BdoaZNr$HU0?-;$%)%y(i=V0T3z`0+Sgzv9&~a!6P~i4_JnB(BOqlX{2_9&h)Xt zi+9uvqkNfS6FXXF_Mr=0BUtPw5r*QNv*48FgU7o*wS27okeG${BbN<`Fyl<8ws%8U z-07Eea8;JjWK_Ql+#@m@JM+m>uU@?GD89u*gHCcRmO*XCdlx+f)QBhx`hP5n@VE#R z2DBLG2lqu?_CP+50zbO~K;Gt$->vUVmY4{7Fq^reEL#(}kqWi*w<|qPkYBV-BQx@s zVm<)A_3c8)xbbGr^s2@WdEMRV=GgFp3i^)suzAOp?mt0|y`;;5k2V7stPoYj$cM$8 zg@c=GztYIixV-9^@_bKDn#1U)&P)yVE^=3o_j;ivYO*s@Blu;z!Qw_}hs+MGK1`Hw z?K=vCSUzm20TE)Vsm|_4senJP8eXO_`F$K20rfq>$1(nZ$8bYff0vs1=FGKB^D&n+y=-zl)|w};$UFbS8w(jR zmadG~#|JONfPBlwBgD;$FyHY^i!>N`K>jLt6#(# zPTvO>GyN`dVKoo59)k$LBpmwuGw;vL!n`~C{vvCw zuWZL4lrsJgR+>Oobw~~)Hx>h!0AETW)q&0v1hrHZN!pruK){c@&nf7_8GT;=F3({& zZKR9shvQsXYbsij49lE40V)>G}!7OgQXnDe$EsxOvo# zhg`3IjM@dR`~rZvFDvVJ#K2_M`UB~zhxm?Q&(uVZ&#qpFYm@0|AQvD-uJdZ89|`fJaSt}PRzC9p zEJuiR6Dw?^)3nmd64bPi*!bcZ%pt?Sp-LDsnqtKkZ}GSU2uEX_fhy#cjnmr4t-8`* zAXOBI!CLTeSR1{qQoK)V5cjRSYTsIBudM^g2)y4njcTMB7H7Ay^Th}_k-y3gaX0Br zC*|Dw;IVQy{QCj44vT~XC7#}oUe`$@5{<4oFhw%6zd>vx$X21J4FolDST%SJM>?5Y zGW)GED2q`B<388*>8`L5GnZQr>{llmEo?|pG~KCSPwND__QpeQiE3D1P;spu6QXstd$ zaD5)wdMJKu33`%6L*0)Sva8qhPaPejj|{qMeTInugup*XO4{$}oz=WYJrg8ERxlt# zj@8TROV>2IScUaUm}+57@q;TXaBCY}_^K5?mVesnXkq59A!Yi9g)HM%(|U#32jH2i z5E0Sh+Pv^Ip~2Qa#P{Kfrsu&-!b+Ss-kgW|Ru+$b*iquS@h@`oP6s{)T=o1!5a#UrFFKwH0XvZHQoIdzE%d~6d9mEU2sZm&Ah4&xw|z9OSPfogf%nXL{*Q2GhDd=cr9tk83PwuzJ3sk)1D{qTR+xKr zFu?h2>2a3d_~jtpEAERCh^w}F|N8FoBWwB%dOvpo*j&{T@Z$d>>i*v|9nIW;L04=T zGiyoA_2k$WHXZjK$BL;rvd9AWY2B0xmK_>`1M8yyT<;`V@#;LHs z&i?l6U=>e92n4X^r*D-6$w{7D>Q-Q&rvh&F9Ml9C^?yp)lDQ1Nxc^(CtO3|*!dCZ+ zbc&`OShm^WijoAlU(}*>`);0HACr3NZ==5^pj1jQc^@gwZCcr&xRV4J@R6HJY<`aZmAlP5QEg(d=p@y;lgXvQm zHJ2|v_GJyp6zuo!K?-1juw?)ATxx73JsFSAdoExsBcOV+3AFKG{Me5r@%{gYxk^uq zHb0|s;+Zncr6}Eh8FEi>TOY|(Pk!j~Yv3kavCL0?+K~uF25`eNpak>X%@vF9e&A{4 zhLRF&9WsmQ?C`jI|7DjtBgXU%(sTns=qT; zo|;=X*YKT7((uw5!yC@d=6U1Qc7r0ZS3L{#SB%SDxWOpe0H-Y(JLaF@TDw1q0yYV7 zTcR{*$Qp$ux#Jj&X&~NQ{x^xmf!K|lY+Dpq z@s|M|)Tk24rKD=aH+v-Qhe|&jIeC!?tN&`3bP308h4Nn&rw07!uh>@oMnW*Suw|5Y zW`Bf?ZalM&Q3}o!o`6iBA*lS%J%&@0_rajt1ZBrkuWD$}q*Gu&{{P9iK*}E#J`sdN zy9WGvSM62?GMf{WpesU22s<~nug=#k@c{g;<{)gWLUqN@O-pMvTJ#tT$A4J_tY^n^ z1Kk&g^uZ@4-ob})a}{phLTlHx)0S4CVtjl+euHy#rMBO^NTj_ajGS7A(aEeYa!e-`(J-- zBN6R?DAoV)Tp3)Sw`@iBBnG%BZny{Y6PLsaCoQFz?wbWlSk8tGUyo`F9>Q67xb1z~A_M zJdt{=5|vg`>kQ|I)5BSq06iV+r2Jm?tNE!E{$3=>PrL5)G^?l0{8>Wba{btsDLk9 zy(5FwzFv9nF8ia@i&Wv38Jrnna{r^}#0hhP!~8i5m+-c}f*A62?y*XReRu-23)e*2 zx>KHTWj=f4sQ*RUeBL+34opCLtDXXZDi*FNIPn&*SQx5Jk}=ry40VsjKjRC8cd1Hb)cSQ0U`fILTV zeJ6B}YCHgKOzYXVha)d%51Hs%yvLb@EBx4;u#0m{rp1H0Hj{-EN6;O0MB*7(E6)Oe z%YYnv1a}*;G`=_`+5|l3BnTG~aH2Or_Nyh4zhO&JUMF9u0?MsP(#Y2Jd)-`!Q$l)r zcaS512P0(NDKpiUcw;2?{3v?@I9#}-xW3yxBmysq7~tI=uR`*8F+>g$3F#Aul1|%l z)Ifu&Nw}%?Vz%J+6&aFGGi~lGKFs51y5M}j7}5c$x_d=}Ys!!vS^wOLX>LXc8`arObit0y3qMPQv{tpuG~&0b!zgx`fhA%j6tz=7)>&bh`E z+2<2b`AunLCPdP)R(jM)4(d- zrO9WjLG($T>$Rm)sbqyAp?^wXbW^+)DV|=4YTQz9z?B-Ry2_DTV2hg^* zNgIRPN#?h8dzns;_OPa^_&&pe{i%pTj~yiV?HQyTs}|qq0Y~IkiJ&Gs13*}1OpV7# zN-!lR8Q~wj=sF~EbzR)L$so@%O0oUavB)ADa5;%>M@h1(-BW1YI zrNo2-i^{_#@y8a6lX9i{1N=2iZm@v-Zbhe1fIQMKhHk%1utkph$SDyO`|?@N+JjVI z-cE0vOcX=}GgbL|R=g3$?GzCZvC46hJ(>DB@r7(r9c=q|PG1TZahFWQA<221o`z7q z_Uer^SOdd&P&3Xt)V})zntmEi`@cpn@8$aKAQIue+l>sha;MZViJ8; zpLK)X^m6;3L;MRdm1i;X;6VoXru_SLbXmT^%Y8}005*&Z<-u$|`~d05E`P7Bn*M_J z#6HxrOhAtt!MzNuWzmwX7qx#T;>&n9IU|Z?Q>*z@=QiJ?3VF?kc3m>Z`}5D^Kh?~- zJ%L(y#nDf`s&qOoY}YQ$Uc*YMK?xtpH(lqQwjz|(6TtF#$^0Nf`gqFM1($-IgS^UB zB~Ks;vpDHicY+rs39Ycue%cy+J?xLC%fGXR2*l8AQw3>MsvwIl<#&a+Nay=uu+md- zM_l^q(hL(eMU9|{A+?;)N>ieT1i*6QePOFWYxVv$rKhTzn{YBVLLD#%5NyIf57C%v z6tMX|Ci)-?uRbami+ROvVr&K%d0%6Mo{SSIC_@_J8e@_D@Id8|2Iobmu2;vT3ABhK zuJ1kw{aVODA_fcHe7g1ZD=y}FHI9mHKM2f0oT!N;V>8C57mIu6$p znX+l|eSIzO_WJ-b@<=daHGO??N`LQVQ;XbnR=gG*$Crf9e||{RhTx^II`_8m^@5V3 z_V&`Rj9)tddXY3ki2j(~?6Z`!vx3-KWx^tc#5~GqnZOE+bb>b23^VwhKrka9!y|rl zwca)zf;f&Ru!iocv=CYGYMA0&XQ=r!AF1^0gi8?`><_8e>R|P%A5<0D^r``mWy*$b z*gW03C7(yY3uErnqt+e#S7k~`)^*Uog*uY2+G zHkJP-C_{mXs*E&PHu;)Q?f2{^08treA8C(H=Yk*F{F8QLBYOm{RS*E>!IX}f+B}g> zRATlbR!Wu-f+3Z7H9=!n4&W<GD9}6-VX<$jq(AOl`##GeU>1m6m0sYx<8~Y6+X_8JQJ7hZ zI#pEuIKsXnM3c*v7^AbgcL?vEwot*@y3xQ_GE`f5{oRwdqxe8p_=%HDk*t!0Anbw; z3CaOu^S5BK^vDeS-%ik; zRc7B@-FVaGM)nk156o9CkO_bXUg|Lk-*TlKOiCX+YB%EC*pS>`D5;a)G+Nhc_WO}DThi{kaC_^;*#X@ z8_~LXxA`Qz)}e1m+PJX)gd=I}VlUq%8nT0U;#czwCy~KVAiET9z3}LzQyo)9H6z zz<>0{hpBtzpC!0xg&vb66k$`&qdXE_%lRJw(UA3hg==Y}g!e!sNHzPU6@~PhU&&Q3 zW=>*hk+0~OvRuaF_|d1%AXhj-xrkP?QdtWfe&qIvVL=C{>@2N}CTMiW4hq<25C zA~>S2K1_~c{NC=lV|#=tGZ4=>KJ*SIO>Gr=c$(6%z00p^M4#ZlZF~D9u%gPv*Z?=q z&P%v}r^IeHl+wGG7K<_XO>CLsQbw5@SPdw&$Hjh~h+aaWDGh@QTFwS-E-ABF8i zxQ|<4W=%eu#2>OlT=~_ z(szvj%1t6(HmLApvKX`b^iE_Dy}%O|qWU-lNW{r}qg@_(q?@Bf)G7}*9XOEk(BSwf54We8;nEl9H7WXY1X zB8+a?DoF}SCL}w_P4>C9A!{X6mO_?n$&zKv=UmeJeZRkd!uL@>^)j#5wO{8t*E!Gg zJO!HLKfd)J#>dZ_i@v<@G6CxP0pk~OT~3e zKqI8$w}rB$$O7k+3ySeWbT7)1A!sqho#p-JNzjTTcQRw13&m>%`$e7Cr}NBhZW9i7 z2f47u2}B0A2iL4Wy2BSBf2JT0N#6HC6-(PjXJNZVh+?jTY-}V5!z**vETbJVwA{-X z!plRbz)+jL?wZosmK552fZvQlCV(sqZI$+6!x5(r(!Su0d2Ap6#0!F-pDXdwDpE5R zFvUGH5WZ;-d^-ayQ@c%^m_s^=P!dtNl_5f~Wq3Z8?uVVUrv0b0b&g|Xa8AAy2xWR$ zJJ4Ue?tu>Uab~E@dq@vdnnBPbcFt`TBE5c-(0qUf>@S$!Oue@;g$&+L{isC|s70N7 zd^lHIWFgWA!B@K^aQ#n2$j;}V9svzq8lfnVCj^)>oEsp6M%Ma1kDgWtr4a`&WIztj zTuQ);Q&EDba6)Ii~=YT#pEbfCy?A z$8kdIBX1A~=pG(pk>sDuKq3nHVS0&0^r72oj<8&&5nMPpw~QBx{S#+(kO2`}F(?nN zeOig`jMv45%9Ng^)N-(Xx5c+3i#>p>?Mm>&8g8SNNtfAC=sbkwgm>&+SX{0|(dm#^ zW*auKF8ibBP$P^$5RAY;{BARZzcHXyYz%_X#0;N%fu2|X6sLTi(P4sX2k^InF+i-T zgtEzKk0E!+A86Bao}KKCM3g7uCuiLiUNxDh%7P3HSw_F9d*=g?6{3drE-rq~i2ero zT*V-E()!LUb4&i!p(pGY)T?$2krno%AQ%(|2{0%izp|{et zxT0WX(4ZtC$}6>2Z*GS-KygXp&?QYfLOHsrr%<}tgW-q}4)4@qeP)6$R4DR$^%Ooq zA@)irGF^@qMRH}YTRGK#l~{*}?Z#qDni&8^(@ti9&1P%D>5V%aq5GPMYQ>)Az8Z{* zA4F3R!bPwFaYXHOdD|&rzrVta{bkojC(8Bxi**Am=K}3iZYj=u5Ag*EOQ&ai!MQyK~JuSEw8+L=v*!3urE{sAcbip4t2*3fGL3K4JRdxsz z@PePkb`!gy6GaE8$Fq(Wc@G(?0;RzKaFife-1Bk+YW$s2-dZqPG~LGJ#TK4!5(=e^ z87MU4LMlO_lNNO0|HF6Cn%U-BvtUjrqzf>hQI6h8%f~MpUb{33jsza{teB&J+`9;52@&!z&%^Jd?|}$=E>1Pf z@173=eP4+61b2nf@y0hgqkrZT(QZs!#sKF@v|xB%9Cf^ohq&#{>#hW}gg?@H94q#J z59bsvfJf{5zk#VFV@+Ji@q5(*kj3o?3KNCl(AV-JE9~_M!q69h{8?!M zi_mKR8m6XkT-!f|_V*L(c%fm9*mc|V72tnf66qOD@@lldC z*4f|Ek zajXI;#C6JiA|0qs>d{F8oNS*iJja8aCxN>bXk?gCcppQePwubXz-V?%vH;9?kso<& zq%K$NBwwa{dK&%;u4Xo=;xoqKTGr+De6&1#$VZ;SOLx1&(i{w+%@^<(Zm*@v_tk+F z$UrRVugk4>_lvs~ih?Hj!%9kZRQBfpkrhMPtv~~IScek)7~E?jyLn>_39Y>VI5H9q zUiM7fALtL*wg`;yzL*1io-T0hq8v8O?#=7BF7tA51|=8;;=-ltz|$=wJeZys28tyw z_Zyc%P>K2#UhjvB#!w|6bStqnRSpJ`fLm0`YtGF-yV<8zV;YXvdWc4HeQ|H#ti`!A zl<6Pf(n0aHN&$1O3m{QAw_0e%L{PLo%5x|k#Loz(&&S9;&C*=OI0B3+7DW{wG`rU0 zT-7oFIJb_md1#r$5U7 z6sz|yLGj1~^*0^hh%SNwf6E-PPFnR$bIc~3ztKp1w^iEKf@2S(FU%FYb{%zT&vvjm z;n@`BRxeik^-xudo!nryMJe!6ya-~X8xmf?ZvU|bAcpLG*I*N+^G;2OjCVdtGarAa z6_U?%_xOG=Yk$GE_QOiw(8Q+*(&d;cE#K;-7@6S~9Dn@uRbiYJZlrd3HV9JfdK)A# zYF1_yj_|yWx^DGJE=$}^CDv;Za210<8LfQdq<>+rde-Ahp0NAT`SwFXTMhdA2w!P| zgG5~l5CWNixbX(-Bm)_Fh8)7#xe5bwy(o;&0PrZlbh`~{`YtP(C#bk}nq)hVZ7eIj zaLLTM!R!TU^eYgn3g{LE7`(mDbtg9_rHGGAY-_fOG=0dz9*}jB64*z zY-K=8(YPhv!zw0D#Z`R(=#V|2z`r~{#$AG6_V7P{)3d8L`#S?1M|b3Kpc@!o-fa>7 zqM{`+k|ksh-=W<@f4`29{Eo6*J^%w=5gg-_pML-#tVZzDVac9n7z_!ctEF)~@SRI! z4ZuMYGPml*zF5e&b%o7d0s~;5Bs~O&15%r1u8nrDDlCX!`Hu+r?r+aVpm zWU?(ddZEt!o1?~y?YurS@TVDxf+OnqEuh)Ic`xBrvY_APUg6qhu#6<;VZlh0%fk#B zOYCAo*!0P7q_W3XZyZb}mxl%k?MoljR`VGqq3{hvcf_IGy^nYHn_~{6$8i+#183*t z$AaN8iZ{3m*NA|+&p`Ygx?3-(2)jxSw`;5zU`$FteaRPn643W0`1QCw-Bw?~U9E$N zJ}*Fuk|wJj2u96lw((xqDKy+wBr+62mM5eq6~K*IS!@OQCJk#&&z6QKcg%U04IhhZ zaXzpzyM)lafbhBv2sOUx%?@d=&%x^Y3E!k}!8ja|?Ca*GnpXe>Tn;pwJr3njirGz{ z$0c_qsRvg_5?bYOGAx}>OCGb)4v_=~DcU^RD(nQI0{H;U~`!!Pu~B4&yAo65Yv*j;SPL@GIh6a05%V!n{@?% zj67QEZizq(f=4tinc>5d=H>lU67TFO1`?i(w<$WhF^VdaU-~XVl^n3Np0#FjfId$M zqwMDM1+B;65)FhwV!Ua^Q2~RNRPS~lu{JP`^II}s1dXZDG(h zI(#+ZURW}v>YNGH>s8~(y&%q!QdxTWXu;>WebT){z2!Y74DjTbxL1CAaXeMMXnASq z5TNpaPywkeQA7qeIL=5+xOe8Q@#fv@6oG51f77sr3MDvZml9dkUqoi#QipJY4TFz>2C*T zzN_D?Y|&|APE|EA%|GQ_OD_wjd#6J%6$YygC&#jJDkoA5IM~jcXDC}W>2K&kRkz#DR@)Pvr3jt}pe zs8djAyxcQYFU>o(cXUL?;BB_atI#=XpEhlT^hTzqbi3zK6&&sPGsos0pI4?Waohz? z=d9G!`;6Shjn}18kTa+cEGQJ~qX^7TGB&`CW@bO8!Y0@^01nA`IAwP8zmH-%@<*ZH zZ0m%GpMF-JWrb;RHbs(%V(9O|=s`ti!iZn6CQDk2FLrobk4AqxD5Gp`BpV4J%Hi>^@1es8+0~ z`r;Zy=-ju|lb*yZaV6(eSy9*H0>Q=hu8#CT6jBLs{@$hfSptsFx0?Q-pQyDKo_u^* z*54^sJ#X|rYtG7wz-v&M;iLfN)BcM15K_GQZc;j&+n!N?NN5!)EsbdiTL?|CRrYYI z$sfyp^J(w-1}ksEcFXd&5irlgH9O%i6oP@}CapB$W+l%_$HOnq-A*7m? z7gBEp(5qxC*YDhQxcQ;j@||1OX5SHYU;o${6Hd;r9ZTYtyobM1vUz40y2a)LK8+`t zCY8?sEkg!=6qK}M(ZL#R!scHwQfM1uLNq}#D{7!Y45d)^O27Md{N)$K90Fl`nkwmC z!aBm2@+C9at%lwv*%TIrUK4tsTgH4`pcVg@kQ~)|QElnL5#}K7$)K4_cL!7NItApV zzF*)BR>(~Dol^FlYKT`z@fzN#ks&$!rajPiVpcynuoRyXpOROo5f)VQSmxZgPjr?@ ziAV1$kO%bhq=6*yg;yfp&7fnGNyr6~$eRX4As8UboYG+$mMGrdh0;ZVZWmpanxA{) z+8+m}l=rxm<0;yg<8qGW22jUuUjin=CM}-KJyc!RSx0_u6)$A;D}pQo-vj6d>$!b9 zAy`Wa`MW)PeEF?kjXHnc*A#G{tD5su$Jmlkku@k9IJ40z3#r_gBkR9QdtLEL@s0la z-Fa@T>r2_+XFq8wZ>puIxtbtRfn2QQO?eH-@OX%CN|NnuNU=w6&sfT=DLa`!coV;NUHM`ca2#lfW%?m0T! zyZAtMaWTMmxP76txoY#33kT-AKVpLR8zuD-I$MMDN~pRjzVaiRw#!V5KN0nIKtiQn z9BfJk70NC!+&Fxt?c>qomOy@yVMhj_&Ed&NLIj0}@j?5XT8M8^RZWYQ^pvs}KhM~v zqyNxuKhd8z9}jnTE4)7^vmsqSeQ2nhIk`9KZOh*I_wOnm&?a|o9uO~*`%G*Es)aVbrb<8o^@A-Or3x3Rd7;Zzi0Hr`&OmSQ}g-*EtIqfL; zkINW{Hp%4>{o_P4eb>=`zy`L!chNj<0Y?xJlVR$1|Y?2E{uH)32N4sGenfQ-^ zwGLS|GLL2OV0V3AK%O;pbpk1J=g9Enwtx*{f4w##JPU;tV8BtY2rHlyOac*Ak%AjBL@}ji^a}uxst(rI0a-1dMhbspKZM)_hB~V>|mo z`3x25ZSmft8LW0f^6Nshz!rWeG*M6--eiL9v_o;r0dRqksCWn+JQ-dETC)xTvXO>_ zq_<&&c969&-T-NuLn*cM1vQyES_^!RYu#8MsQkB0V)ekGFX=$K&q&$JnZ`& zfwmo|aP|S*jRrtK{74Ku>cYpAh|sMs&|5oqn=n@37m5cgCpBk9?qqQcEN;YPeawc4 zi&Z-)po%X-VQUuRs@$lKkI1SgXWb0D3#rTbM5+i%DFqDnka;=gJr|knAKGDURua)1 zI&@>FhzOWJd67}hmc{W23{Mf;`5YGMNPrpXflF?n=#@N5yCAfuBpY)97rW0)U>{xy z+8Dzo_a4wq|2D{?{tfp8e;xZekAlM>+m%@SDX+*)5QFlC;aW?c26hrjd5%?vy@;z> z!_shrK>9pe3WfvjZ-;haEeA`Zt79{PSu&Dk7+8JLO^7*s99w@+>Z<7f>Y|&_O0Z@1 z2kRu6WQ_B>Wo`X=wlz2fyxd-3sMH4^)HOva>k(|ooyaQ(NZRDd=ZYp%uwT4Tt0P!o zikvzltzUUxo`dO&_Z61k_XG_-`0ErN$0kWNtf8;7h~~mm z@J0q2sA-2%K0C!p`uq z7HN~@l_{PBcL4ikCe~E(zQ(0p2I7=%{{d!t6!pQ!Ni9=d4VRLDh0}5S)ADMmC|qfO ziM=>_nAV{k7Rub%194F4fbz?T>jR{Ly{YuL27gFpVi>#jRm!=fYv-(y=|&702a!$S z3t;R)1Ai!(==KHzg%F)B5wqpAM>#Mskx5Q4Fd7z=uV8N=M(n|s#!HTMek@uDJD(uZI1B#$PNIp|ZzxNruXl zN9%Q{)TtF9z-$1bd~n50M^%Egtq4N;-a6+#Ijx?fKb`;qQNk|Zp^dH3yR*sbs!A^2)v203 z(e$w(5VD%3H~`y((z%IK7XY7$P|;cUJMDvdynhHv;V7H+ijgLa3AqHFP~5$$k&-6y zmGv!^AMnLn!7HF>f>%-Md&XxJ2R8Qcqr8VdWRh%JG`9RxnWWIRu{dXWvD^0t@*x3N za3U~u8Y9aRTsjUrOqSN((kVW_m09U>BqnCtj`wKgP$=kh`i@Q~3`AwdxF=Ek^m14? zff^ubO}QYwOAHUJ;KuO*Dm9HMjt)Z@*xT94)dTK*S{gH~pXRR@vN7huc}?*iS5 zS_3x9RG;LS>uAPVt?mv8NHT@p&POFrlEaOYL)$8(7N?!~u`_7%Xaq~AGz z^T&mj_`Hy<|G7rQ7{DCW7ptWK8y|_thP&*>Z#?9RsFSx)zHqh56H_b4|xUd>Pnx0fC814aZ=P2q*R)r@$vSt)ydIgqk3Hz^Fpaknr{Au%5 za7rUywI|U5do{>6*G&MQtQVm9^+5?f?DY>MRX;HYdE&dk>dS6#UrIb*07$vzxarda z9X(IX7pnHXty)6iCTyR8#js@+0m?6HS_g&>M7mMI@uFfjU0iU}$0jKNm)onH8qOPP%Ytp5pf$~}0Go_3oOK6}SI<{9o= zs#1TrcNJ;FmB(Il-dEciLLDqh41yL%r23~Gs^y*~Y8?vBDE`GB3$r-|N`lS))p;e` z5l##pVRa2QjMpbKtiQ83`5+R?0q_&-RR1suR8)Eu*J$joE#{i4Cgg)77%3~;44XfN zH3x_UKCod}8h2L5zXi;S(_Med_71qH%*>tvzN%DBN=TDx%NPhX%*HtttO z6H2lHQK-1_=S~NXZT`VprLr?003)095eHd1tYt>1lAF;{cDPo9o(*gV_dg9=_4OFc zEIv;9K#StE0`BOyV(20Gi^an z>a)F_Sq+`-fEu2ncp_xA%l~#;!CXhPbxymF8oU<|xomm#CVTexf(Hb|D+p*7l?Ut4 z^>8b3xCIHKHYSu94iRCyPA6_+&ZAZgNsnZgh1H*0vYoUd$hKoTUSi zhfsJBZ1!nr8k)&1v6Fs>}gL;7gqvyCXep{un z%~*N=IGm%}uZBmWs#bDkzb{%QpSP?BdDYuA517HEUO)_4zxdQKsjd38r!{c=ghm^S z;JLk*HiPm_Z5sy9Iwph&Hw(gz9mDJ30d$=tiiHI2u)SGa+EbRT(nGMpK<)qGbvM5@&Qozo{fKV`*R7OF5s#Ta(;J_ z9&Gs!sCoIrUmI4yMAKu4=I4jVaF6r{ zSNyt@c}DXAHL^=xqJQpv**!T~)EzIL%#9`KzB`S$8U9VAgg!wPvVq=o*Jdz%RTd!) zXGg|q>gqa1Ba;j@yp=n|ScSD9a_bW-r$kmoKC1HvWLD}E=I2Del1ZEZjK~0w-df2K zh)p{}Aj*AJjotKE@H15{VZ{Hs_i5yrf>r)qE!pt(rWqpS8*Gd*JI@DDo{-<&>jJsOr5ol z!nVQV>HG|MBwIkGCF$MatL=tDjG+)>>mU?D*l3IiRiJ!h``Nk`f;49F=B=B&Br^31 zd?cUo84_pw{EZpE9`o)JD&h{jb!WKj_OIenDLAUOYbn%3!$5nOINA^W{D`Iqk53!h zkHe#rK2&LJmM72gvb!Br@p~8B46jynu;8buDhCVup=O4fSBkkdqavKPz!FFuI{B+OG-Mkdb)%Y?pgkOl$G5J1^b|cRn;nBksvJ~m@p|IG&v_?0 zp>;$U_JTq}*`+bH8)-k#FhJ{qVZ`b64Z1Knt=jO>#$%q6YgN&3xOJE_8mNfF@Ydm( zQ~JM(LqoQu!~$xvp+I}EVJ_l7Ke9=HN82whGVoX|0hQ{%5Bxc|SR5xz6erfw5W0I7 zYWjf_NI}CuAP!%L$yGO3-3CwP(9&T$nP#oEs!ou}SQ|N11T_-gioAE`=Q6_L5;$P7DbkFut$oCC3?3U#9oB}Lzzv3R)2-nCIqziZeJDhX`U(mm%2P1}YfL{( z462LHV~m-Rl9ht3N8Si6wKRC1_^ajYuo!chQ4uQemP(tq+^^!$kX`9?M>S!gJuCv* zH-3H;VhfL9i8)#D*m@f(m2;jKTia?XFi|RaA5|!%7Hay$ZgUY019VXxMsLM_FdYpF zTJcx1_VBG$WrM{sQ~pM6{VgO$aeauPz{Mqdd3oXS@~qix@X(8;Ss+3?n(PLNF9(6D zCQaQec<2gRVCAq5hfPHde{F^z4`@K|+i-oedIO4X1sa6CVkWnU?Pjwzz>}@elA^9K ztE%Qn4qqK?RTy@MpGyQ!n9-sJ*;T{aoLqR=$wY!IgHt2^8K_8Mssecq6Yd52j438^-)RCSs2O_dgBTthNul zqh>5Kq3?E}^(jWhL_~DO{M@KCWBjO8$NXfk!qWWY=Bkcr{}%d_YH0tiat8Qx8^GvX z#Rxv7@k9Z79CUz;of1A&efd}Y{2@R@57~n2;b4IJqZCm`opF^uN6B0Fe$3 zr%%6l#Tb`|iuZ1Zp+37OTIbj9Jw%nqitfL7Vs4^at)vwD)j~TuDXdI?pmLnTS^u1MP~v9pw4`rI>(ez_sf1H z?P;%}?YEoXZT;2zlRTMvl+l_|sTt(Ehd}3Te0+Rqyj1_#o`H7E_ux@=H8r)xdIq4_ zmIrEX*>{IRuY*ltbvf?aC$u(YHrsKtHVXcJUna!{<00ByBdER`)3^z9|ObZ3>$)eV}I1nT{~; zz`Z>rn(c2wv$Jmh^Q|@bJHF_ z#U%Bor}VFZ_6}LlCeJ!SwmAjl#{#_8Ho)OaLk?KMz*NRiOUkQo?#&GlWZVGL)c~)( znwgpTh|QkN>fO+~d|WgHwdw^(A%XbHrT-`s#LV88*1=q~$)2K>DYDUWuvz0gI=WF; zCL9MsQB#n#e}S9zSeL-C4PgYeE-*~@eVd2V28(+NN*{-;(VNwm)@B0UW`+Z0!!NnB zcHZFO_`+%K+VY>bR{sM_>3{z~B7zD?Aaq5n)x7pAoWHC;oAT>*GH6I7lKs|SU0M5; znIiH_POdcy3tgeaTsyQT4c2~LkMaB8KM;O5@aKU29>L#h@b@P9EoOd=z|Yg{x1h%2 zexD?(hw|^U82?*Y_^odJQXhXStUopU-x@XL_nqXovheGc`deA}tt|ZiXZo1p7Q(~} UNR=3jVc?&xwxL$Orp=}Q12R%Fy#N3J literal 0 HcmV?d00001 diff --git a/Targets/D3N/Resources/Assets.xcassets/Logo.imageset/Contents.json b/Targets/D3N/Resources/Assets.xcassets/Logo.imageset/Contents.json new file mode 100644 index 0000000..d7aaf31 --- /dev/null +++ b/Targets/D3N/Resources/Assets.xcassets/Logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "1024.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Targets/D3N/Sources/App/RootStore.swift b/Targets/D3N/Sources/App/RootStore.swift index 522b94c..7a24954 100644 --- a/Targets/D3N/Sources/App/RootStore.swift +++ b/Targets/D3N/Sources/App/RootStore.swift @@ -17,7 +17,7 @@ struct RootStore: Reducer { init() { self = .onboarding(.init()) - self = .mainTab(.init()) +// self = .mainTab(.init()) } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift index 1135af7..124215d 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift @@ -12,23 +12,43 @@ import ComposableArchitecture public struct OnboardingNicknameStore: Reducer { public struct State: Equatable { + @BindingState var focus: Field? = .nickname + @BindingState var nickname: String = "" + public init() { } + + enum Field: Hashable { + case nickname + } } - public enum Action: Equatable { + public enum Action: BindableAction, Equatable { + case binding(BindingAction) case onAppear + case confirmButtonTapped + case delegate(Delegate) public enum Delegate: Equatable { - case select(NewsEntity) + case confirm(String) } } public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in switch action { case .onAppear: + state.focus = .nickname + return .none + // 닉네임, 생년, 성별, 관심카테고리 + case .confirmButtonTapped: + if !state.nickname.isEmpty { + state.focus = nil + return .send(.delegate(.confirm(state.nickname))) + } return .none default: diff --git a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift index 6263420..b6b5029 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift @@ -12,13 +12,45 @@ import ComposableArchitecture struct OnboardingNicknameView: View { let store: StoreOf + @FocusState var focus: OnboardingNicknameStore.State.Field? + + init(store: StoreOf) { + self.store = store + } var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - VStack { + VStack(alignment: .leading) { Text("닉네임을 입력해주세요.") - .font(.largeTitle) + .font(.title) + .fontWeight(.bold) + + TextField("닉네임", text: viewStore.$nickname) + .focused( + self.$focus, + equals: .nickname + ) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + HStack { + Spacer() + + Button("완료") { + viewStore.send(.confirmButtonTapped) + } + } + } + } + .onAppear { + viewStore.send(.onAppear) + } + + Spacer() } + .navigationBarBackButtonHidden() + .padding() + .padding(.vertical, 40) + .bind(viewStore.$focus, to: self.$focus) } } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift index bd2773d..eec7569 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift @@ -54,6 +54,13 @@ public struct OnboardingNavigationStackStore: Reducer { case .onAppear: return .none + case let .signUp(.delegate(action)): + switch action { + case .signIn: + state.path.append(.nickname(.init())) + return .none + } + default: return .none } diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift index fda9ad0..342a36e 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift @@ -17,11 +17,13 @@ public struct OnboardingSignUpStore: Reducer { public enum Action: Equatable { case onAppear + + case signInWithAppleButtonTapped case delegate(Delegate) public enum Delegate: Equatable { - case select(NewsEntity) + case signIn } } @@ -31,6 +33,9 @@ public struct OnboardingSignUpStore: Reducer { case .onAppear: return .none + case .signInWithAppleButtonTapped: + return .send(.delegate(.signIn)) + default: return .none } diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index 8b53f39..0633ad1 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -7,20 +7,51 @@ // import SwiftUI +import AuthenticationServices import ComposableArchitecture public struct OnboardingSignUpView: View { let store: StoreOf + public init(store: StoreOf) { self.store = store } + @State private var rotateIn3D = false + public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in VStack { - Text("") + Spacer() + + Image("Logo") + .resizable() + .frame(width: 100, height: 100) + .cornerRadius(22) + .foregroundColor(.white) + .rotation3DEffect( + .degrees(rotateIn3D ? 12 : -12), + axis: (x: rotateIn3D ? 90 : -45, y: rotateIn3D ? -45 : -90, z: 0) + ) + .task { + withAnimation(Animation.easeInOut(duration: 5).repeatForever(autoreverses: true)) { + rotateIn3D.toggle() + } + } + + Spacer() + + Text("매일 3개의 뉴스를 읽고 독해력을 기르세요.") + .font(.caption2) + + SignInWithAppleButton(onRequest: {_ in }, onCompletion: {_ in }) + .frame(height: 50, alignment: .center) + .padding() + .onTapGesture { + viewStore.send(.signInWithAppleButtonTapped) + } } } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift index 0c44cb3..25ba0d3 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift @@ -7,3 +7,53 @@ // import Foundation + +import ComposableArchitecture + +public struct OnboardingUserInfoStore: Reducer { + public struct State: Equatable { + @BindingState var focus: Field? = .nickname + @BindingState var nickname: String = "" + + public init() { } + + enum Field: Hashable { + case nickname + } + } + + public enum Action: BindableAction, Equatable { + case binding(BindingAction) + case onAppear + + case confirmButtonTapped + + case delegate(Delegate) + + public enum Delegate: Equatable { + case confirm(String) + } + } + + public var body: some ReducerOf { + BindingReducer() + + Reduce { state, action in + switch action { + case .onAppear: + state.focus = .nickname + return .none + // 닉네임, 생년, 성별, 관심카테고리 + case .confirmButtonTapped: + if !state.nickname.isEmpty { + state.focus = nil + return .send(.delegate(.confirm(state.nickname))) + } + return .none + + default: + return .none + } + } + } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift index c729389..d1c9fb5 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift @@ -6,4 +6,29 @@ // Copyright © 2023 sju. All rights reserved. // -import Foundation +import SwiftUI + +import ComposableArchitecture + +struct OnboardingUserInfoView: View { + let store: StoreOf + + init(store: StoreOf) { + self.store = store + } + + var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + VStack(alignment: .leading) { + Text("닉네임을 입력해주세요.") + .font(.title) + .fontWeight(.bold) + + Spacer() + } + .navigationBarBackButtonHidden() + .padding() + .padding(.vertical, 40) + } + } +} From 8f43952077e2630ecfa380ab236b12104ee5f2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Fri, 27 Oct 2023 13:03:08 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20signUp=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20ui=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Targets/D3N/Sources/Config/D3N-Info.plist | 2 +- .../SignUp/OnboardingSignUpView.swift | 59 +++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Targets/D3N/Sources/Config/D3N-Info.plist b/Targets/D3N/Sources/Config/D3N-Info.plist index a345875..21c0d4f 100644 --- a/Targets/D3N/Sources/Config/D3N-Info.plist +++ b/Targets/D3N/Sources/Config/D3N-Info.plist @@ -3,7 +3,7 @@ UIUserInterfaceStyle - Light + Auto NSUserTrackingUsageDescription Daily3News 앱에서 사용자에게 맞춤 광고를 위해 추적 권한을 요청합니다 UIRequiresFullScreen diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index 0633ad1..fe636fb 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -24,6 +24,33 @@ public struct OnboardingSignUpView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in VStack { + + VStack { + Text(""" + 권리와 의무의 주체가 될 수 있는 자격을 권리 능력이라 합니다. 사람은 태어나면서 저절로 권리 능력을 갖게 되고 생존하는 내내 보유합니다. 그리하여 사람은 재산에 대한 소유권의 주체가 되며 다른 사람에 대하여 채권을 누리기도 하고 채무를 지기도 합니다. 사람들의 결합체인 단체도 일정한 요건을 갖추면 법으로써 부여되는 권리 능력인 법인격을 취득할 수 있습니다. 단체 중에는 사람들이 일정한 목적을 갖고 결합한 조직체로서 구성원과 구별되어 독자적 실체로서 존재하며 운영기구를 두어 구성원의 가입과 탈퇴에 관계없이 존속하는 단체가 있습니다. 이를 사단이라 하며 사단이 갖춘 이러한 성질을 사단성이라 합니다. + 사단의 구성원은 사원이라 하며 사단은 법인으로 등기되어야 법인격이 생기는데 법인격을 가진 사단을 사단 법인이라 부릅니다. 반면에 사단성을 갖추고도 법인으로 등기하지 않은 사단은 법인이 아닌 사단이라 합니다. 사람과 법인만이 권리 능력을 가지며 사람의 권리 능력과 법인격은 엄격히 구별됩니다. 그리하여 사단 법인이 자기 이름으로 진 빚은 사단이 가진 재산으로 갚아야 하는 것이지 사원 개인에게까지 책임이 미치지 않습니다. + 회사도 사단의 성격을 갖는 법인이며 주식회사는 주주들로 구성되며 주주들은 보유한 주식의 비율만큼 회사에 대한 지분을 갖습니다. 그런데 2001년에 개정된 상법은 한 사람이 전액을 출자하여 일인 주주로 회사를 설립할 수 있도록 하였습니다. 사단성을 갖추지 못했다고 할 만한 형태의 법인을 인정한 것이며 여러 주주가 있던 회사가 주식의 상속, 매매, 양도 등으로 모든 주식이 한 사람의 소유로 되는 경우가 있습니다. 이런 일인 주식회사에서는 일인 주주가 회사의 대표 이사가 되는 사례가 많으며 이처럼 일인 주주가 회사를 대표하는 기관이 되면 경영의 주체가 개인인지 회사인지 모호해집니다. + 구성원인 사람의 인격과 법인으로서의 법인격이 잘 분간되지 않는 경우에는 간혹 문제가 발생할 수 있습니다. 상법상 회사는 이사들로 이루어진 이사회만을 업무 집행의 의결 기관으로 두며 대표 이사는 이사 중 한 명으로 이사회에 서 선출되는 기관입니다. 이사의 선임과 이사의 보수는 주주 총회에서 결정하도록 되어 있습니다. 그러나 주주가 한 사람뿐이면 사실상 그의 뜻대로 될 뿐 이사회나 주주 총회의 기능은 퇴색하기 쉽습니다. 심한 경우에는 회사에서 발생한 이익이 대표 이사인 주주에게 귀속되고 회사 자체는 허울만 남는 일도 발생할 수 있습니다. 이처럼 회사의 운영이 주주 한 사람의 개인 사업과 다름없이 이루어지고 회사라는 이름과 형식은 장식에 지나지지 않는 경우에는 회사와 거래 관계에 있는 사람들이 재산상 피해를 입는 문제가 발생할 수 있습니다. 이때 특정한 거래 관계에 관련하여서만 예외적으로 회사의 법인격을 일시적으로 부인하고 회사와 주주를 동일시해야 한다는 법인격 부인론이 제기됩니다. 법률은 이에 대하여 명시적으로 규정하고 있지 않지만 법원은 권리 남용의 조항을 끌어들여 이를 받아들인다 회사가 일인 주주에게 완전히 지배되어 회사의 회계 주주 총회나 이사회 운영이 적법하게 작동하지 못하는데도 회사에만 책임을 묻는 것은 법인 제도가 남용되는 사례라고 보는 것이다. + """) + .font(.caption2) + + Text( + """ + + 윗글에서 설명한 주식회사에 대한 이해로 가장 적절한 것은 ? + + ① 대표 이사는 주식회사를 대표하는 기관이다. + ② 일인 주식회사는 대표 이사가 법인격을 갖는다. + ③ 주식회사의 이사회에서 이사의 보수를 결정한다. + ④ 주식회사에서는 주주 총회가 업무 집행의 의결 기관이다. + ⑤ 여러 주주들이 모여 설립된 주식회사가 일인 주식회사로 바뀔 수 없다. + """ + ) + .font(.caption2) + } + .padding(.horizontal, 10) + .foregroundStyle(Color(uiColor: .systemGray)) + Spacer() Image("Logo") @@ -31,6 +58,7 @@ public struct OnboardingSignUpView: View { .frame(width: 100, height: 100) .cornerRadius(22) .foregroundColor(.white) + .shadow(color: Color(uiColor: .systemGray5), radius: 20) .rotation3DEffect( .degrees(rotateIn3D ? 12 : -12), axis: (x: rotateIn3D ? 90 : -45, y: rotateIn3D ? -45 : -90, z: 0) @@ -46,12 +74,33 @@ public struct OnboardingSignUpView: View { Text("매일 3개의 뉴스를 읽고 독해력을 기르세요.") .font(.caption2) - SignInWithAppleButton(onRequest: {_ in }, onCompletion: {_ in }) - .frame(height: 50, alignment: .center) - .padding() - .onTapGesture { - viewStore.send(.signInWithAppleButtonTapped) + SignInWithAppleButton(onRequest: {_ in }, onCompletion: { result in + switch result { + case .success(let authResults): + print("Apple Login Successful") + switch authResults.credential{ + case let appleIDCredential as ASAuthorizationAppleIDCredential: + // 계정 정보 가져오기 + let UserIdentifier = appleIDCredential.user + let fullName = appleIDCredential.fullName + let name = (fullName?.familyName ?? "") + (fullName?.givenName ?? "") + let email = appleIDCredential.email + let IdentityToken = String(data: appleIDCredential.identityToken!, encoding: .utf8) + let AuthorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8) + print(IdentityToken, AuthorizationCode) + default: + break + } + case .failure(let error): + print(error.localizedDescription) + print("error") } + }) + .frame(height: 50, alignment: .center) + .padding() + .onTapGesture { + viewStore.send(.signInWithAppleButtonTapped) + } } } } From 922d196fc2a2d95f3c2118dca9345b124b385f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Fri, 27 Oct 2023 16:02:35 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20entitlements=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=95=88=EB=90=98=EA=B2=8C=20edit=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Onboarding/SignUp/OnboardingSignUpView.swift | 6 +++--- Tuist/ProjectDescriptionHelpers/Project+Templates.swift | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index fe636fb..1f81d8a 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -98,9 +98,9 @@ public struct OnboardingSignUpView: View { }) .frame(height: 50, alignment: .center) .padding() - .onTapGesture { - viewStore.send(.signInWithAppleButtonTapped) - } +// .onTapGesture { +// viewStore.send(.signInWithAppleButtonTapped) +// } } } } diff --git a/Tuist/ProjectDescriptionHelpers/Project+Templates.swift b/Tuist/ProjectDescriptionHelpers/Project+Templates.swift index 7d5ae63..43a875a 100644 --- a/Tuist/ProjectDescriptionHelpers/Project+Templates.swift +++ b/Tuist/ProjectDescriptionHelpers/Project+Templates.swift @@ -70,6 +70,7 @@ extension Project { infoPlist: .file(path: .relativeToRoot("Targets/\(name)/Sources/Config/D3N-Info.plist")), sources: ["Targets/\(name)/Sources/**"], resources: ["Targets/\(name)/Resources/**"], + entitlements: .file(path: .relativeToRoot("D3N.entitlements")), dependencies: dependencies, settings: .settings( base: SettingsDictionary().otherLinkerFlags(["-ObjC"]), From 8260dc89ea5ab8edb15649181dfe3e5d56934afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 31 Oct 2023 11:19:48 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20user=20info=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Onboarding/OnboardingNavigationStackStore.swift | 12 ++++++++++++ .../Onboarding/OnboardingNavigationStackView.swift | 6 ++++++ .../Onboarding/SignUp/OnboardingSignUpView.swift | 11 +++++++---- .../UserInfo/OnboardingUserInfoStore.swift | 6 +----- .../Onboarding/UserInfo/OnboardingUserInfoView.swift | 4 ++++ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift index eec7569..200d635 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackStore.swift @@ -33,16 +33,21 @@ public struct OnboardingNavigationStackStore: Reducer { public struct Path: Reducer { public enum State: Equatable { case nickname(OnboardingNicknameStore.State) + case userInfo(OnboardingUserInfoStore.State) } public enum Action: Equatable { case nickname(OnboardingNicknameStore.Action) + case userInfo(OnboardingUserInfoStore.Action) } public var body: some Reducer { Scope(state: /State.nickname, action: /Action.nickname) { OnboardingNicknameStore() } + Scope(state: /State.userInfo, action: /Action.userInfo) { + OnboardingUserInfoStore() + } } } @@ -61,6 +66,13 @@ public struct OnboardingNavigationStackStore: Reducer { return .none } + case let .path(.element(id: _, action: .nickname(.delegate(action)))): + switch action { + case .confirm: + state.path.append(.userInfo(.init())) + return .none + } + default: return .none } diff --git a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift index 0be1035..762cbf0 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/OnboardingNavigationStackView.swift @@ -33,6 +33,12 @@ public struct OnboardingNavigationStackView: View { action: OnboardingNavigationStackStore.Path.Action.nickname, then: OnboardingNicknameView.init(store:) ) + case .userInfo: + CaseLet( + /OnboardingNavigationStackStore.Path.State.userInfo, + action: OnboardingNavigationStackStore.Path.Action.userInfo, + then: OnboardingUserInfoView.init(store:) + ) } } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index 1f81d8a..17169a9 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -87,7 +87,10 @@ public struct OnboardingSignUpView: View { let email = appleIDCredential.email let IdentityToken = String(data: appleIDCredential.identityToken!, encoding: .utf8) let AuthorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8) - print(IdentityToken, AuthorizationCode) + print("=======IdentityToken======") + print(IdentityToken!) + print("=======AuthorizationCode======") + print(AuthorizationCode!) default: break } @@ -98,9 +101,9 @@ public struct OnboardingSignUpView: View { }) .frame(height: 50, alignment: .center) .padding() -// .onTapGesture { -// viewStore.send(.signInWithAppleButtonTapped) -// } + .onTapGesture { + viewStore.send(.signInWithAppleButtonTapped) + } } } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift index 25ba0d3..b40c0ba 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoStore.swift @@ -13,7 +13,7 @@ import ComposableArchitecture public struct OnboardingUserInfoStore: Reducer { public struct State: Equatable { @BindingState var focus: Field? = .nickname - @BindingState var nickname: String = "" + @BindingState var date: Date = Date() public init() { } @@ -45,10 +45,6 @@ public struct OnboardingUserInfoStore: Reducer { return .none // 닉네임, 생년, 성별, 관심카테고리 case .confirmButtonTapped: - if !state.nickname.isEmpty { - state.focus = nil - return .send(.delegate(.confirm(state.nickname))) - } return .none default: diff --git a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift index d1c9fb5..35526fb 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/UserInfo/OnboardingUserInfoView.swift @@ -24,6 +24,10 @@ struct OnboardingUserInfoView: View { .font(.title) .fontWeight(.bold) + DatePicker(selection: viewStore.$date) { + + } + Spacer() } .navigationBarBackButtonHidden() From 752355a644b3252ef3767b3fab93f1ea4b8fd50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sat, 4 Nov 2023 17:46:50 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{Shared => Config}/Environment.swift | 0 .../LocalStorage/LocalStorageRepository.swift | 28 ++++++--- .../D3N/Sources/Domain/Auth/AuthClient.swift | 60 +++++++++++++++++++ .../D3N/Sources/Domain/Auth/AuthService.swift | 49 +++++++++++++++ .../Auth/DTO/AppleLoginRequestDTO.swift | 18 ++++++ .../Auth/DTO/AppleLoginResponseDTO.swift | 23 +++++++ .../Domain/Auth/Entity/AuthEntity.swift | 14 +++++ .../D3N/Sources/Domain/D3NAPIProvider.swift | 50 ++++++++++++++++ .../Sources/Domain/NetworkLoggerPlugin.swift | 41 +++++++++++++ .../Domain/News/Entity/NewsEntity.swift | 4 +- .../D3N/Sources/Domain/News/NewsClient.swift | 28 +++++---- .../Feature/News/List/NewsListStore.swift | 2 +- .../SignUp/OnboardingSignUpView.swift | 27 --------- .../Feature/Quiz/Main/QuizMainStore.swift | 2 +- .../Feature/Today/Main/TodayMainStore.swift | 2 +- 15 files changed, 295 insertions(+), 53 deletions(-) rename Targets/D3N/Sources/{Shared => Config}/Environment.swift (100%) create mode 100644 Targets/D3N/Sources/Domain/Auth/AuthClient.swift create mode 100644 Targets/D3N/Sources/Domain/Auth/AuthService.swift create mode 100644 Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginRequestDTO.swift create mode 100644 Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift create mode 100644 Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift create mode 100644 Targets/D3N/Sources/Domain/D3NAPIProvider.swift create mode 100644 Targets/D3N/Sources/Domain/NetworkLoggerPlugin.swift diff --git a/Targets/D3N/Sources/Shared/Environment.swift b/Targets/D3N/Sources/Config/Environment.swift similarity index 100% rename from Targets/D3N/Sources/Shared/Environment.swift rename to Targets/D3N/Sources/Config/Environment.swift diff --git a/Targets/D3N/Sources/Core/LocalStorage/LocalStorageRepository.swift b/Targets/D3N/Sources/Core/LocalStorage/LocalStorageRepository.swift index da6c849..e70fed4 100644 --- a/Targets/D3N/Sources/Core/LocalStorage/LocalStorageRepository.swift +++ b/Targets/D3N/Sources/Core/LocalStorage/LocalStorageRepository.swift @@ -8,18 +8,28 @@ import Foundation -public struct LocalStorageRepository { - enum Keys: String { - case alreadySolvedNewsIds +public struct LocalStorageManager { + public enum Key: String { + case accessToken + case refreshToken } - public static func saveAlreadySolvedNewsIds(ids: [Int]) { - var newIds = loadAlreadySolvedNewsIds() + ids - UserDefaults.standard.set(newIds, forKey: Keys.alreadySolvedNewsIds.rawValue) + public static func save(_ key: Key, value: String) { + UserDefaults.standard.set(value, forKey: key.rawValue) } - public static func loadAlreadySolvedNewsIds() -> [Int] { - let ids = UserDefaults.standard.object(forKey: Keys.alreadySolvedNewsIds.rawValue) as? [Int] - return ids ?? [] + public static func load(_ key: Key) -> String { + let value = UserDefaults.standard.object(forKey: key.rawValue) as? String + return value ?? "" } + +// public static func saveAlreadySolvedNewsIds(ids: [Int]) { +// var newIds = loadAlreadySolvedNewsIds() + ids +// UserDefaults.standard.set(newIds, forKey: Keys.alreadySolvedNewsIds.rawValue) +// } +// +// public static func loadAlreadySolvedNewsIds() -> [Int] { +// let ids = UserDefaults.standard.object(forKey: Keys.alreadySolvedNewsIds.rawValue) as? [Int] +// return ids ?? [] +// } } diff --git a/Targets/D3N/Sources/Domain/Auth/AuthClient.swift b/Targets/D3N/Sources/Domain/Auth/AuthClient.swift new file mode 100644 index 0000000..4d3669b --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/AuthClient.swift @@ -0,0 +1,60 @@ +// +// AuthClient.swift +// D3N +// +// Created by 송영모 on 11/3/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import ComposableArchitecture +import Moya + +struct AuthClient { + var appleLogin: (_ code: String, _ idToken: String) async -> Result + var appleUnlink: () async -> Result + var refresh: () async -> Result +} + +extension AuthClient: TestDependencyKey { + static let previewValue = Self( + appleLogin: { code, idToken in + return .success(.init(accessToken: "", refreshToken: "")) + }, + appleUnlink: { .init(.success(.init(accessToken: "", refreshToken: ""))) }, + refresh: { .init(.success(.init(accessToken: "", refreshToken: ""))) } + ) + + static let testValue = Self( + appleLogin: unimplemented("\(Self.self).appleLogin"), + appleUnlink: unimplemented("\(Self.self).appleUnlink"), + refresh: unimplemented("\(Self.self).refresh") + ) +} + +extension DependencyValues { + var authClient: AuthClient { + get { self[AuthClient.self] } + set { self[AuthClient.self] = newValue } + } +} + +// MARK: - Live API implementation + +extension AuthClient: DependencyKey { + static let liveValue = AuthClient( + appleLogin: { code, idToken in + let target: TargetType = AuthService.appleUnlink + let response: Result = await D3NAPIkProvider.reqeust(target: target) + return response.map { $0.toEntity() } + }, + appleUnlink: { + return .success(.init(accessToken: "", refreshToken: "")) + + }, + refresh: { + return .success(.init(accessToken: "", refreshToken: "")) + } + ) +} diff --git a/Targets/D3N/Sources/Domain/Auth/AuthService.swift b/Targets/D3N/Sources/Domain/Auth/AuthService.swift new file mode 100644 index 0000000..54a2def --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/AuthService.swift @@ -0,0 +1,49 @@ +// +// AuthService.swift +// D3N +// +// Created by 송영모 on 11/3/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation +import Moya + +public enum AuthService { + case appleLogin(code: String, idToken: String) + case appleUnlink + case refresh +} + +extension AuthService: TargetType { + public var baseURL: URL { URL(string: Environment.baseURL + "/auth")! } + + public var path: String { + switch self { + case .appleLogin: return "/apple/login" + case .appleUnlink: return "/apple/unlink" + case .refresh: return "/refresh" + } + } + public var method: Moya.Method { + switch self { + case .appleLogin: return .get + case .appleUnlink: return .delete + case .refresh: return .get + } + } + public var task: Task { + switch self { + case let .appleLogin(code: code, idToken: idToken): + return .requestJSONEncodable(AppleLoginRequestDTO(code: code, idToken: idToken)) + case .appleUnlink: + return .requestPlain + case .refresh: + return .requestPlain + } + } + + public var headers: [String: String]? { + return ["Authorization": "Bearer \(LocalStorageManager.load(.accessToken))"] + } +} diff --git a/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginRequestDTO.swift b/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginRequestDTO.swift new file mode 100644 index 0000000..a5fafae --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginRequestDTO.swift @@ -0,0 +1,18 @@ +// +// AppleLoginRequestDTO.swift +// D3N +// +// Created by 송영모 on 11/3/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +struct AppleLoginRequestDTO: Codable { + let code, idToken: String + + enum CodingKeys: String, CodingKey { + case code + case idToken = "id_token" + } +} diff --git a/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift b/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift new file mode 100644 index 0000000..2d06f1e --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift @@ -0,0 +1,23 @@ +// +// AuthResponseDTO.swift +// D3N +// +// Created by 송영모 on 11/3/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +struct AppleLoginResponseDTO: Codable { + let appToken: String + let refreshToken: String +} + +extension AppleLoginResponseDTO { + func toEntity() -> AuthEntity { + return .init( + accessToken: self.appToken, + refreshToken: self.refreshToken + ) + } +} diff --git a/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift b/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift new file mode 100644 index 0000000..5748013 --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift @@ -0,0 +1,14 @@ +// +// AuthEntity.swift +// D3N +// +// Created by 송영모 on 11/3/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +struct AuthEntity: Codable { + let accessToken: String + let refreshToken: String +} diff --git a/Targets/D3N/Sources/Domain/D3NAPIProvider.swift b/Targets/D3N/Sources/Domain/D3NAPIProvider.swift new file mode 100644 index 0000000..e65e658 --- /dev/null +++ b/Targets/D3N/Sources/Domain/D3NAPIProvider.swift @@ -0,0 +1,50 @@ +// +// NetworkProvider.swift +// D3N +// +// Created by 송영모 on 11/4/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import Moya + +public enum D3NAPIError: Error { + case unAuth + case jsonParseError + case none +} + +final class D3NAPIkProvider { + private static let provider = MoyaProvider(plugins: [D3NNetworkLoggerPlugin()]) + + static func reqeust(target: TargetType) async -> Result{ + let response = await self.request(target: target) + switch response { + case .success(let success): + switch success.statusCode { + case 200..<300: break + case 300..<400: break + case 401: return .failure(.unAuth) + default: break + } + + if let response = try? JSONDecoder().decode(R.self, from: success.data) { + return .success(response) + } else { + return .failure(.jsonParseError) + } + case .failure(let failure): + return .failure(.none) + } + } + + private static func request(target: T) async -> Result { + await withCheckedContinuation { continuation in + provider.request(MultiTarget(target)) { result in + continuation.resume(returning: result) + } + } + } +} diff --git a/Targets/D3N/Sources/Domain/NetworkLoggerPlugin.swift b/Targets/D3N/Sources/Domain/NetworkLoggerPlugin.swift new file mode 100644 index 0000000..414a8f7 --- /dev/null +++ b/Targets/D3N/Sources/Domain/NetworkLoggerPlugin.swift @@ -0,0 +1,41 @@ +// +// NetworkLoggerPlugin.swift +// D3N +// +// Created by 송영모 on 11/4/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import Moya + +final class D3NNetworkLoggerPlugin: PluginType { + func willSend(_ request: RequestType, target _: TargetType) { + guard let request = request.request, let method = request.method else { + print("Invalid Request") + return + } + print("==============[willSend]==============") + print("[Logger - ✅ Header] \(request.headers)") + print("[Logger - ✅ Body] \(String(describing: String(data: request.httpBody ?? Data(), encoding: .utf8)))") + print("[Logger - ✅ Endpoint] String(describing: [\(method.rawValue)] - \(String(describing: request.url)))") + } + + func didReceive(_ result: Result, target: TargetType) { + print("==============[didReceive]==============") + print("[Logger - ✅ request] \(target.baseURL)/\(target.path)") + + switch result { + case let .success(response): + print("[Logger - ✅ result] ⭕️ SUCCESS") + if let json = String(bytes: response.data, encoding: .utf8) { + print("📌 statusCode: \(response.statusCode)") + print(json) + } + case let .failure(error): + print("[Logger - ✅ result] ❌ FAILURE") + print(error) + } + } +} diff --git a/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift b/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift index af40eaf..ab2a221 100644 --- a/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift +++ b/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift @@ -20,7 +20,7 @@ public struct NewsEntity: Equatable { let mediaCompanyName: String //FIXME: 풀었던 뉴스 아이디 저장 로직 내부 구현 - let isAlreadySolved: Bool + let isAlreadySolved: Bool = false init( id: Int, @@ -44,6 +44,6 @@ public struct NewsEntity: Equatable { self.mediaCompanyName = mediaCompanyName //FIXME: 풀었던 뉴스 아이디 저장 로직 내부 구현 - self.isAlreadySolved = LocalStorageRepository.loadAlreadySolvedNewsIds().contains(id) +// self.isAlreadySolved = LocalStorageRepository.loadAlreadySolvedNewsIds().contains(id) } } diff --git a/Targets/D3N/Sources/Domain/News/NewsClient.swift b/Targets/D3N/Sources/Domain/News/NewsClient.swift index 5226af0..dc425de 100644 --- a/Targets/D3N/Sources/Domain/News/NewsClient.swift +++ b/Targets/D3N/Sources/Domain/News/NewsClient.swift @@ -16,13 +16,13 @@ public enum NewsError: Error, Equatable { } struct NewsClient { - var fetchNewsList: (Int, Int) async -> Result<[NewsEntity], NewsError> + var fetchNewsList: (Int, Int) async -> Result<[NewsEntity], D3NAPIError> var fetchQuizList: (Int) async -> Result<[QuizEntity], NewsError> } extension NewsClient: TestDependencyKey { static let previewValue = Self( - fetchNewsList: { _, _ in .failure(.no) }, + fetchNewsList: { _, _ in .failure(.none) }, fetchQuizList: { _ in .failure(.no) } ) @@ -44,16 +44,20 @@ extension DependencyValues { extension NewsClient: DependencyKey { static let liveValue = NewsClient( fetchNewsList: { pageIndex, pageSize in - let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) - let response = await provider.request(.fetchNewsList(pageIndex: pageIndex, pageSize: pageSize)) - switch response { - case .success(let success): - let model = try? JSONDecoder().decode(PageModel.self, from: success.data) - let news = model.map(\.result)?.map(\.content)?.map { $0.toDomain() } ?? [] - return .success(news) - case .failure(let failure): - return .failure(.no) - } + let target: TargetType = NewsService.fetchNewsList(pageIndex: pageIndex, pageSize: pageSize) + let response: Result, D3NAPIError> = await D3NAPIkProvider.reqeust(target: target) +// let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) +// let response = await provider.request(.fetchNewsList(pageIndex: pageIndex, pageSize: pageSize)) +// let tt = + return response.map(\.content).map { $0.map { $0.toDomain() } } +// switch response { +// case .success(let success): +// let model = try? JSONDecoder().decode(PageModel.self, from: success.data) +// let news = model.map(\.result)?.map(\.content)?.map { $0.toDomain() } ?? [] +// return .success(news) +// case .failure(let failure): +// return .failure(.no) +// } }, fetchQuizList: { newsId in let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) diff --git a/Targets/D3N/Sources/Feature/News/List/NewsListStore.swift b/Targets/D3N/Sources/Feature/News/List/NewsListStore.swift index 47427a0..caff6c8 100644 --- a/Targets/D3N/Sources/Feature/News/List/NewsListStore.swift +++ b/Targets/D3N/Sources/Feature/News/List/NewsListStore.swift @@ -34,7 +34,7 @@ public struct NewsListStore: Reducer { case onAppear case fetchNewsListRequest - case fetchNewsListResponse(Result<[NewsEntity], NewsError>) + case fetchNewsListResponse(Result<[NewsEntity], D3NAPIError>) case newsListItems(id: NewsListItemCellStore.State.ID, action: NewsListItemCellStore.Action) case delegate(Delegate) diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index 17169a9..51a2123 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -24,33 +24,6 @@ public struct OnboardingSignUpView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in VStack { - - VStack { - Text(""" - 권리와 의무의 주체가 될 수 있는 자격을 권리 능력이라 합니다. 사람은 태어나면서 저절로 권리 능력을 갖게 되고 생존하는 내내 보유합니다. 그리하여 사람은 재산에 대한 소유권의 주체가 되며 다른 사람에 대하여 채권을 누리기도 하고 채무를 지기도 합니다. 사람들의 결합체인 단체도 일정한 요건을 갖추면 법으로써 부여되는 권리 능력인 법인격을 취득할 수 있습니다. 단체 중에는 사람들이 일정한 목적을 갖고 결합한 조직체로서 구성원과 구별되어 독자적 실체로서 존재하며 운영기구를 두어 구성원의 가입과 탈퇴에 관계없이 존속하는 단체가 있습니다. 이를 사단이라 하며 사단이 갖춘 이러한 성질을 사단성이라 합니다. - 사단의 구성원은 사원이라 하며 사단은 법인으로 등기되어야 법인격이 생기는데 법인격을 가진 사단을 사단 법인이라 부릅니다. 반면에 사단성을 갖추고도 법인으로 등기하지 않은 사단은 법인이 아닌 사단이라 합니다. 사람과 법인만이 권리 능력을 가지며 사람의 권리 능력과 법인격은 엄격히 구별됩니다. 그리하여 사단 법인이 자기 이름으로 진 빚은 사단이 가진 재산으로 갚아야 하는 것이지 사원 개인에게까지 책임이 미치지 않습니다. - 회사도 사단의 성격을 갖는 법인이며 주식회사는 주주들로 구성되며 주주들은 보유한 주식의 비율만큼 회사에 대한 지분을 갖습니다. 그런데 2001년에 개정된 상법은 한 사람이 전액을 출자하여 일인 주주로 회사를 설립할 수 있도록 하였습니다. 사단성을 갖추지 못했다고 할 만한 형태의 법인을 인정한 것이며 여러 주주가 있던 회사가 주식의 상속, 매매, 양도 등으로 모든 주식이 한 사람의 소유로 되는 경우가 있습니다. 이런 일인 주식회사에서는 일인 주주가 회사의 대표 이사가 되는 사례가 많으며 이처럼 일인 주주가 회사를 대표하는 기관이 되면 경영의 주체가 개인인지 회사인지 모호해집니다. - 구성원인 사람의 인격과 법인으로서의 법인격이 잘 분간되지 않는 경우에는 간혹 문제가 발생할 수 있습니다. 상법상 회사는 이사들로 이루어진 이사회만을 업무 집행의 의결 기관으로 두며 대표 이사는 이사 중 한 명으로 이사회에 서 선출되는 기관입니다. 이사의 선임과 이사의 보수는 주주 총회에서 결정하도록 되어 있습니다. 그러나 주주가 한 사람뿐이면 사실상 그의 뜻대로 될 뿐 이사회나 주주 총회의 기능은 퇴색하기 쉽습니다. 심한 경우에는 회사에서 발생한 이익이 대표 이사인 주주에게 귀속되고 회사 자체는 허울만 남는 일도 발생할 수 있습니다. 이처럼 회사의 운영이 주주 한 사람의 개인 사업과 다름없이 이루어지고 회사라는 이름과 형식은 장식에 지나지지 않는 경우에는 회사와 거래 관계에 있는 사람들이 재산상 피해를 입는 문제가 발생할 수 있습니다. 이때 특정한 거래 관계에 관련하여서만 예외적으로 회사의 법인격을 일시적으로 부인하고 회사와 주주를 동일시해야 한다는 법인격 부인론이 제기됩니다. 법률은 이에 대하여 명시적으로 규정하고 있지 않지만 법원은 권리 남용의 조항을 끌어들여 이를 받아들인다 회사가 일인 주주에게 완전히 지배되어 회사의 회계 주주 총회나 이사회 운영이 적법하게 작동하지 못하는데도 회사에만 책임을 묻는 것은 법인 제도가 남용되는 사례라고 보는 것이다. - """) - .font(.caption2) - - Text( - """ - - 윗글에서 설명한 주식회사에 대한 이해로 가장 적절한 것은 ? - - ① 대표 이사는 주식회사를 대표하는 기관이다. - ② 일인 주식회사는 대표 이사가 법인격을 갖는다. - ③ 주식회사의 이사회에서 이사의 보수를 결정한다. - ④ 주식회사에서는 주주 총회가 업무 집행의 의결 기관이다. - ⑤ 여러 주주들이 모여 설립된 주식회사가 일인 주식회사로 바뀔 수 없다. - """ - ) - .font(.caption2) - } - .padding(.horizontal, 10) - .foregroundStyle(Color(uiColor: .systemGray)) - Spacer() Image("Logo") diff --git a/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainStore.swift b/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainStore.swift index 8d83576..8d0c64d 100644 --- a/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainStore.swift +++ b/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainStore.swift @@ -68,7 +68,7 @@ public struct QuizMainStore: Reducer { state.quizEntityList = quizEntityList state.quizList = nil //FIXME: 풀었던 뉴스 아이디 저장 로직 내부 구현 - LocalStorageRepository.saveAlreadySolvedNewsIds(ids: [state.newsEntity.id]) +// LocalStorageRepository.saveAlreadySolvedNewsIds(ids: [state.newsEntity.id]) return .send(.delegate(.solved(quizEntityList))) } diff --git a/Targets/D3N/Sources/Feature/Today/Main/TodayMainStore.swift b/Targets/D3N/Sources/Feature/Today/Main/TodayMainStore.swift index 1811f66..152e561 100644 --- a/Targets/D3N/Sources/Feature/Today/Main/TodayMainStore.swift +++ b/Targets/D3N/Sources/Feature/Today/Main/TodayMainStore.swift @@ -32,7 +32,7 @@ public struct TodayMainStore: Reducer { case allNewsButtonTapped case fetchNewsListRequest - case fetchNewsListResponse(Result<[NewsEntity], NewsError>) + case fetchNewsListResponse(Result<[NewsEntity], D3NAPIError>) case newsListItems(id: NewsListItemCellStore.State.ID, action: NewsListItemCellStore.Action) From 4cb892061624d759e397a93139621f1136e4f6ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sat, 4 Nov 2023 22:46:10 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20api?= =?UTF-8?q?=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../D3N/Sources/Domain/Auth/AuthService.swift | 5 +++ .../Auth/DTO/AppleLoginResponseDTO.swift | 7 +++- .../Auth/DTO/UserOnboardRequestDTO.swift | 16 +++++++++ .../Auth/DTO/UserOnboardResponseDTO.swift | 36 +++++++++++++++++++ .../Domain/Auth/Model/GenderType.swift | 15 ++++++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardRequestDTO.swift create mode 100644 Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift create mode 100644 Targets/D3N/Sources/Domain/Auth/Model/GenderType.swift diff --git a/Targets/D3N/Sources/Domain/Auth/AuthService.swift b/Targets/D3N/Sources/Domain/Auth/AuthService.swift index 54a2def..5ad3355 100644 --- a/Targets/D3N/Sources/Domain/Auth/AuthService.swift +++ b/Targets/D3N/Sources/Domain/Auth/AuthService.swift @@ -13,6 +13,7 @@ public enum AuthService { case appleLogin(code: String, idToken: String) case appleUnlink case refresh + case userOnboard(nickname: String, gender: Gender, birthYear: Int, categoryList: [NewsField]) } extension AuthService: TargetType { @@ -23,6 +24,7 @@ extension AuthService: TargetType { case .appleLogin: return "/apple/login" case .appleUnlink: return "/apple/unlink" case .refresh: return "/refresh" + case .userOnboard: return "/user/onboard" } } public var method: Moya.Method { @@ -30,6 +32,7 @@ extension AuthService: TargetType { case .appleLogin: return .get case .appleUnlink: return .delete case .refresh: return .get + case .userOnboard: return .post } } public var task: Task { @@ -40,6 +43,8 @@ extension AuthService: TargetType { return .requestPlain case .refresh: return .requestPlain + case let .userOnboard(nickname: nickname, gender: gender, birthYear: birthYear, categoryList: categoryList): + return .requestJSONEncodable(UserOnboardRequestDTO(nickname: nickname, gender: gender, birthYear: birthYear, categoryList: categoryList)) } } diff --git a/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift b/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift index 2d06f1e..bb73386 100644 --- a/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift +++ b/Targets/D3N/Sources/Domain/Auth/DTO/AppleLoginResponseDTO.swift @@ -14,7 +14,12 @@ struct AppleLoginResponseDTO: Codable { } extension AppleLoginResponseDTO { - func toEntity() -> AuthEntity { + func toEntity(isShouldSaveAtLocalStorage: Bool = true) -> AuthEntity { + if isShouldSaveAtLocalStorage { + LocalStorageManager.save(.accessToken, value: self.appToken) + LocalStorageManager.save(.refreshToken, value: self.refreshToken) + } + return .init( accessToken: self.appToken, refreshToken: self.refreshToken diff --git a/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardRequestDTO.swift b/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardRequestDTO.swift new file mode 100644 index 0000000..5116889 --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardRequestDTO.swift @@ -0,0 +1,16 @@ +// +// UserOnboardRequestDTO.swift +// D3N +// +// Created by 송영모 on 11/4/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +struct UserOnboardRequestDTO: Codable { + let nickname: String + let gender: Gender + let birthYear: Int + let categoryList: [NewsField] +} diff --git a/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift b/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift new file mode 100644 index 0000000..717176d --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift @@ -0,0 +1,36 @@ +// +// UserOnboardResponseDTO.swift +// D3N +// +// Created by 송영모 on 11/4/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import Foundation + +// MARK: - Welcome +struct UserOnboardResponseDTO: Codable { + let createdAt, modifiedAt, id, nickname: String + let gender: Gender + let birthYear: Int + let categoryList: [NewsField] + let scrapList: [Int] + let appleRefreshToken, refreshToken, memberProvider, roleType: String + let solvedQuizList: [UserOnboardResponseSolvedQuizDTO] +} + +struct UserOnboardResponseSolvedQuizDTO: Codable { + let createdAt, modifiedAt: String + let id: Int + let user: String + let quizID, selectedAnswer, quizAnswer: Int + let quizResult: Bool + + enum CodingKeys: String, CodingKey { + case createdAt, modifiedAt, id, user + case quizID = "quizId" + case selectedAnswer, quizAnswer, quizResult + } +} diff --git a/Targets/D3N/Sources/Domain/Auth/Model/GenderType.swift b/Targets/D3N/Sources/Domain/Auth/Model/GenderType.swift new file mode 100644 index 0000000..e03d26d --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/Model/GenderType.swift @@ -0,0 +1,15 @@ +// +// GenderType.swift +// D3N +// +// Created by 송영모 on 11/4/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +public enum Gender: String, Codable { + case man = "MAN" + case woman = "WOMAN" + case notSelected = "NOT_SELECTED" +} From e68714afd3479d428d5faec3a67828a5705f56f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sat, 4 Nov 2023 23:13:54 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20=EB=B3=B4=EB=83=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Targets/D3N/Sources/App/RootApp.swift | 1 - .../D3N/Sources/Domain/Auth/AuthClient.swift | 14 +++++++++++--- .../D3N/Sources/Domain/Auth/AuthService.swift | 12 ++++++------ .../Auth/DTO/UserOnboardResponseDTO.swift | 13 +++++++++++-- .../Domain/Auth/Entity/AuthEntity.swift | 2 +- .../Domain/Auth/Entity/UserEntity.swift | 16 ++++++++++++++++ .../SignUp/OnboardingSignUpStore.swift | 18 ++++++++++++++++-- .../SignUp/OnboardingSignUpView.swift | 14 ++++---------- 8 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 Targets/D3N/Sources/Domain/Auth/Entity/UserEntity.swift diff --git a/Targets/D3N/Sources/App/RootApp.swift b/Targets/D3N/Sources/App/RootApp.swift index 281d8fe..f11f33f 100644 --- a/Targets/D3N/Sources/App/RootApp.swift +++ b/Targets/D3N/Sources/App/RootApp.swift @@ -19,7 +19,6 @@ struct RootApp: App { RootView( store: Store(initialState: RootStore.State()) { RootStore() - ._printChanges() } ) .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in diff --git a/Targets/D3N/Sources/Domain/Auth/AuthClient.swift b/Targets/D3N/Sources/Domain/Auth/AuthClient.swift index 4d3669b..bacd70d 100644 --- a/Targets/D3N/Sources/Domain/Auth/AuthClient.swift +++ b/Targets/D3N/Sources/Domain/Auth/AuthClient.swift @@ -15,6 +15,7 @@ struct AuthClient { var appleLogin: (_ code: String, _ idToken: String) async -> Result var appleUnlink: () async -> Result var refresh: () async -> Result + var userOnboard: (_ nickname: String, _ gender: Gender, _ birthYear: Int, _ categoryList: [NewsField]) async -> Result } extension AuthClient: TestDependencyKey { @@ -23,13 +24,15 @@ extension AuthClient: TestDependencyKey { return .success(.init(accessToken: "", refreshToken: "")) }, appleUnlink: { .init(.success(.init(accessToken: "", refreshToken: ""))) }, - refresh: { .init(.success(.init(accessToken: "", refreshToken: ""))) } + refresh: { .init(.success(.init(accessToken: "", refreshToken: ""))) }, + userOnboard: { nickname, gender, birthYear, categoryList in .failure(.none) } ) static let testValue = Self( appleLogin: unimplemented("\(Self.self).appleLogin"), appleUnlink: unimplemented("\(Self.self).appleUnlink"), - refresh: unimplemented("\(Self.self).refresh") + refresh: unimplemented("\(Self.self).refresh"), + userOnboard: unimplemented("\(Self.self).userOnboard") ) } @@ -45,7 +48,7 @@ extension DependencyValues { extension AuthClient: DependencyKey { static let liveValue = AuthClient( appleLogin: { code, idToken in - let target: TargetType = AuthService.appleUnlink + let target: TargetType = AuthService.appleLogin(code: code, idToken: idToken) let response: Result = await D3NAPIkProvider.reqeust(target: target) return response.map { $0.toEntity() } }, @@ -55,6 +58,11 @@ extension AuthClient: DependencyKey { }, refresh: { return .success(.init(accessToken: "", refreshToken: "")) + }, + userOnboard: { nickname, gender, birthYear, categoryList in + let target: TargetType = AuthService.userOnboard(nickname: nickname, gender: gender, birthYear: birthYear, categoryList: categoryList) + let response: Result = await D3NAPIkProvider.reqeust(target: target) + return response.map { $0.toEntity() } } ) } diff --git a/Targets/D3N/Sources/Domain/Auth/AuthService.swift b/Targets/D3N/Sources/Domain/Auth/AuthService.swift index 5ad3355..2c5a1ab 100644 --- a/Targets/D3N/Sources/Domain/Auth/AuthService.swift +++ b/Targets/D3N/Sources/Domain/Auth/AuthService.swift @@ -17,19 +17,19 @@ public enum AuthService { } extension AuthService: TargetType { - public var baseURL: URL { URL(string: Environment.baseURL + "/auth")! } + public var baseURL: URL { URL(string: Environment.baseURL)! } public var path: String { switch self { - case .appleLogin: return "/apple/login" - case .appleUnlink: return "/apple/unlink" - case .refresh: return "/refresh" - case .userOnboard: return "/user/onboard" + case .appleLogin: return "auth/apple/login" + case .appleUnlink: return "auth/apple/unlink" + case .refresh: return "auth/refresh" + case .userOnboard: return "user/onboard" } } public var method: Moya.Method { switch self { - case .appleLogin: return .get + case .appleLogin: return .post case .appleUnlink: return .delete case .refresh: return .get case .userOnboard: return .post diff --git a/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift b/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift index 717176d..124ca53 100644 --- a/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift +++ b/Targets/D3N/Sources/Domain/Auth/DTO/UserOnboardResponseDTO.swift @@ -10,7 +10,6 @@ import Foundation import Foundation -// MARK: - Welcome struct UserOnboardResponseDTO: Codable { let createdAt, modifiedAt, id, nickname: String let gender: Gender @@ -21,7 +20,17 @@ struct UserOnboardResponseDTO: Codable { let solvedQuizList: [UserOnboardResponseSolvedQuizDTO] } -struct UserOnboardResponseSolvedQuizDTO: Codable { +extension UserOnboardResponseDTO { + func toEntity() -> UserEntity { + return .init( + nickname: self.nickname, + gender: self.gender, + birthYear: self.birthYear, + categoryList: self.categoryList) + } +} + +internal struct UserOnboardResponseSolvedQuizDTO: Codable { let createdAt, modifiedAt: String let id: Int let user: String diff --git a/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift b/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift index 5748013..25268ac 100644 --- a/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift +++ b/Targets/D3N/Sources/Domain/Auth/Entity/AuthEntity.swift @@ -8,7 +8,7 @@ import Foundation -struct AuthEntity: Codable { +public struct AuthEntity: Codable, Equatable { let accessToken: String let refreshToken: String } diff --git a/Targets/D3N/Sources/Domain/Auth/Entity/UserEntity.swift b/Targets/D3N/Sources/Domain/Auth/Entity/UserEntity.swift new file mode 100644 index 0000000..2c0d8d1 --- /dev/null +++ b/Targets/D3N/Sources/Domain/Auth/Entity/UserEntity.swift @@ -0,0 +1,16 @@ +// +// UserEntity.swift +// D3N +// +// Created by 송영모 on 11/4/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +struct UserEntity: Codable { + let nickname: String + let gender: Gender + let birthYear: Int + let categoryList: [NewsField] +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift index 342a36e..bedee89 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpStore.swift @@ -18,7 +18,10 @@ public struct OnboardingSignUpStore: Reducer { public enum Action: Equatable { case onAppear - case signInWithAppleButtonTapped + case signIn(code: String, idToken: String) + + case appleLoginRequest(code: String, idToken: String) + case appleLoginResponse(Result) case delegate(Delegate) @@ -27,13 +30,24 @@ public struct OnboardingSignUpStore: Reducer { } } + @Dependency(\.authClient) var authClient + public var body: some ReducerOf { Reduce { state, action in switch action { case .onAppear: return .none - case .signInWithAppleButtonTapped: + case let .signIn(code: code, idToken: idToken): + return .send(.appleLoginRequest(code: code, idToken: idToken)) + + case let .appleLoginRequest(code: code, idToken: idToken): + return .run { send in + let response = await authClient.appleLogin(code, idToken) + await send(.appleLoginResponse(response)) + } + + case .appleLoginResponse(.success): return .send(.delegate(.signIn)) default: diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index 51a2123..423fd66 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -53,17 +53,14 @@ public struct OnboardingSignUpView: View { print("Apple Login Successful") switch authResults.credential{ case let appleIDCredential as ASAuthorizationAppleIDCredential: - // 계정 정보 가져오기 let UserIdentifier = appleIDCredential.user let fullName = appleIDCredential.fullName let name = (fullName?.familyName ?? "") + (fullName?.givenName ?? "") let email = appleIDCredential.email - let IdentityToken = String(data: appleIDCredential.identityToken!, encoding: .utf8) - let AuthorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8) - print("=======IdentityToken======") - print(IdentityToken!) - print("=======AuthorizationCode======") - print(AuthorizationCode!) + let idToken = String(data: appleIDCredential.identityToken!, encoding: .utf8) ?? "" + let code = String(data: appleIDCredential.authorizationCode!, encoding: .utf8) ?? "" + + viewStore.send(.signIn(code: code, idToken: idToken)) default: break } @@ -74,9 +71,6 @@ public struct OnboardingSignUpView: View { }) .frame(height: 50, alignment: .center) .padding() - .onTapGesture { - viewStore.send(.signInWithAppleButtonTapped) - } } } } From a6022f4db1975fc357233f7a73c59ef12c4f773b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sat, 4 Nov 2023 23:16:41 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Onboarding/SignUp/OnboardingSignUpView.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift index 423fd66..098bd49 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/SignUp/OnboardingSignUpView.swift @@ -50,13 +50,8 @@ public struct OnboardingSignUpView: View { SignInWithAppleButton(onRequest: {_ in }, onCompletion: { result in switch result { case .success(let authResults): - print("Apple Login Successful") switch authResults.credential{ case let appleIDCredential as ASAuthorizationAppleIDCredential: - let UserIdentifier = appleIDCredential.user - let fullName = appleIDCredential.fullName - let name = (fullName?.familyName ?? "") + (fullName?.givenName ?? "") - let email = appleIDCredential.email let idToken = String(data: appleIDCredential.identityToken!, encoding: .utf8) ?? "" let code = String(data: appleIDCredential.authorizationCode!, encoding: .utf8) ?? "" @@ -66,7 +61,6 @@ public struct OnboardingSignUpView: View { } case .failure(let error): print(error.localizedDescription) - print("error") } }) .frame(height: 50, alignment: .center)