From ff7144f7080b2c7c5ec90624c5c1774107a3a9bb Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Wed, 6 Dec 2023 16:43:41 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A3=B0=EB=A0=9B=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EB=B7=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../btn_add.imageset/Contents.json | 21 +++ .../btn_add.imageset/btn_add.png | Bin 0 -> 3632 bytes .../Contents.json | 21 +++ .../btn_minus_round_rect.png | Bin 0 -> 2438 bytes .../Contents.json | 21 +++ .../btn_plus_round_rect.png | Bin 0 -> 2874 bytes SodaLive/Sources/Live/Room/LiveRoomView.swift | 7 + .../Sources/Live/Room/LiveRoomViewModel.swift | 2 + .../Routlette/Config/RouletteOption.swift | 19 +++ .../Config/RouletteSettingsOptionView.swift | 78 +++++++++ .../Config/RouletteSettingsView.swift | 158 ++++++++++++++++++ .../Config/RouletteSettingsViewModel.swift | 121 ++++++++++++++ .../Room/Routlette/GetRouletteResponse.swift | 17 ++ .../Live/Room/Routlette/RouletteApi.swift | 51 ++++++ .../Room/Routlette/RouletteRepository.swift | 20 +++ 15 files changed, 536 insertions(+) create mode 100644 SodaLive/Resources/Assets.xcassets/btn_add.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/btn_add.imageset/btn_add.png create mode 100644 SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/btn_minus_round_rect.png create mode 100644 SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/btn_plus_round_rect.png create mode 100644 SodaLive/Sources/Live/Room/Routlette/Config/RouletteOption.swift create mode 100644 SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsOptionView.swift create mode 100644 SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift create mode 100644 SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift create mode 100644 SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift create mode 100644 SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift create mode 100644 SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift diff --git a/SodaLive/Resources/Assets.xcassets/btn_add.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/btn_add.imageset/Contents.json new file mode 100644 index 0000000..eef99b1 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/btn_add.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_add.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/btn_add.imageset/btn_add.png b/SodaLive/Resources/Assets.xcassets/btn_add.imageset/btn_add.png new file mode 100644 index 0000000000000000000000000000000000000000..b36e9b56700e8558d56a911dc4b818addfe4d46d GIT binary patch literal 3632 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 Y7}4}FA;!crG=T>g>Y5?m=s3px9}9<$o&W#< literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/Contents.json new file mode 100644 index 0000000..b214365 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_minus_round_rect.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/btn_minus_round_rect.png b/SodaLive/Resources/Assets.xcassets/btn_minus_round_rect.imageset/btn_minus_round_rect.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6a88e2742f1e865ea57bc318cef43ca38bb04d GIT binary patch literal 2438 zcmb7G`9Bkm8=uL2tuf?Il24bTXvo;iRm?dohVo_Q3Y#NGCR>h3j>d=22TO8Ixe~&J z5IH{1+?3fY9cag>@d^2^vv)q4K@vgwPMKFm)AOkdbUf zdgaiKyC;K`x$Kaw{5%5Pr$zZ-mqSsi3um5!Zzk}kyC&#rI!DP>YAMK`%Fw`$l>f@k z3Ej6$?HZmjnYEU2ata9v2|Hq=o6FXAG@Q?D=3U$iByxE)rE1o@0|-5p;-tfArS8bf zTAfL4;4H83sypcZhqcW%ByA#GNH{r5@6AkYfV%V8WsHp&2rfY;W>6|kT~7L=5qs7Y zUZg4>K#~p|rWNrxA1-nq1pUC*>+C-l#uZf8strpmm{JQFI;;kHozVM_{Ijy7Y#d3 znUIqe_Rq%lau*C*Yjl&{)|{*BP$E*x?aX+=q9>%W-MyQ~v0kf6AYZ;)PWu^M*!aTN z&ev%m?8}Lj&@^-Z)(V$m*;V}7^=C%Zby(?n@SfR5yZRVq-(lvB2iayHMG2*TBSwQ0 zCBy>8ABBigzJv|DFN*$kT^q3g6@VHNr~ZXLGvA5D_J(~k8`Z%b5{D3lrX|8FXP^)V z*fsbp7MO{mQy&|qqx!FCTNRwkESyJ8=jB2Lky`W8kX$3)JN(6K(*$d^T2*0N6(43g zH$wpz4bskED?lV{Z8Pp!6hzhh)2IvR{$D3TI};DyM0m6??5$&x^n0>+5+f zI@yo}XB|R8s~$VUVh-KJa>`TBn)p10Ti{FRmwY1#?HD`Rw@oi817~~PABrLyIlVWA zyfAg0bW}8bn9Y(-AnX+C)6Z+CKSVC0rrq+Ifyl;ICmB#D>&R8(pSUyzeh!d!dHYD5 z?22C5oHAad6pMBJX{4tMDlM{$RwR?Oc)&d%b2Vcns&Fhx>%{*K(xsB__xD`lwd*)bmCF*2q;pA*=MXg7YQfbQ zOy9a^NKYp;ZXU0C+RYC^o{@7z?ul0_zOZ*P_g8#!OGl`#%v8)$KVjg7;{!bHxYJ9yOrRW$3GA?>|_j~MyK0Q(2)|NKWmBM0G= zR&VN%$iKJd+tunYn|_pua(x=zn|1?7TR6BWuyNCq7U34XsDfZNJU~AR^^=R6P^+~n>AdD- zZ|s)R0Mv}m&-J$E4rfIbg?DiV>D}8=;pK;A`0zH(Pd>1d1oDS4P$AJ@ysWft65Gok z5iv6vf)gFFf4#x&PS&ij#h=*i8I;P)cA>PW6=9!+CEpEC4dSPO_g%}4)JyB=%&)KC zj&xA#M4qf&$ZpT|yQiXxlQjs__#-r`OBtpRa@U*<03mpk}EOQPUtg7gU^C#`p7IDrTN5L#1cTe?nBow!@2{2GIDgg3Dj&_#sE)mOKIG76INzb$d0F`# z;FU)3E;fcs8Cx%jt~!rnnN10q#Qy#K`_Jm;+*otI67sM-#P&?2oEOGZdYUlq&f!?> z#|~=I2^rGosblv^w!-@C_`BDi0|%#i_r=QnZ+@9nqrR$>T!vTvA+bvNAaA7dq&SxN z?aTVYt83&Acvd(-b;G}{Jj0}p!Gb$>y>i;8k)s!;I8<=D+RqFXELh0iDnJsJ4 zzy1huqj&y3C%*SiE_bH3jSin9-i4^j$H$}dJ2pa$&JK+P-i93}@PDVf$Uu^}K5j5s zL1(44$>rP?E1V($5o6%kU#%=YA-1f%6w7z!_sQT|;alV~kk03<`6c`}6=&qZ8+=dF zb|EbRZt*l;)XLCVIwd&DevVF$tRBhWl1_WHK^Puv>se9txVH=h>HRQt>Y1&=ph6Aj zKNpsHi_)zPZ)3g+(1m6TYL^AT!p-eV6Q>RqdU#H`+3t*BTx5^cPl)2Qm^BMwxgO1g zCLO{Yb?hK~4=ZsP!)I@qU&IyGVgn1)MTv=nADc z*O*x*s>+)@@QO~Z(dUU%;HB{m!P#^L_2O9)X!oG{SxG{T*27ay1G)ER*LJQX+{zZb U%?eiMyiEWL6BP0_!sF3@0sU->DF6Tf literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/Contents.json new file mode 100644 index 0000000..6fc7e4e --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_plus_round_rect.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/btn_plus_round_rect.png b/SodaLive/Resources/Assets.xcassets/btn_plus_round_rect.imageset/btn_plus_round_rect.png new file mode 100644 index 0000000000000000000000000000000000000000..a22155e15cc787c862ac41799a63c67a8745b371 GIT binary patch literal 2874 zcmV-A3&r$_P)Z$xWcdQDUiT)FydpoCn0gqJUI4$Ct`=lb)T3LQ&aO%o)&T-*U|_t~dHbFX2f#4)@)Pwyfr=_)aMobP=vqWul~DbYwE^x(65tiP28$O%83Ag9|!Z>7?!gs~x9M{(vaorO={|2r(tT!Df{P_T~vVd!{#$jw}ZV6laI^!(BR z3dRVwIVjMrb8o<{^QZ4Bi&+b3S@-RH1}cNQ?W`&<{*mdy@>rbXV+pnW+~TXesXU13 zF<{hMI9F^6gmrc+UW(J>_aP>8~>xQqJp(k z;nww);r&3{%eZ>2ZQ0l{HC&kw+CNYzStPWc4`2HktZ)aRVEsVr>VEN*?Qn#OMT1Tv zi!}T4-a@1Vu*=qJM*6HWSE3ZkY?EA(iuNcu{9F}x&(&p9jqz6k~SV(Gnq zh$U)7xo!2~%BM5$LP5Ux%#N>ejkq6CdI-}ivp1k1U*MDmgi!hfWrX$M%AL9YLP5U3 z`6mKCs!r|MLxo$OKP+bg>7<}Jgb?uw+GZv|OX}`?6#a!_j4AVpyp4hOXe*R#MY33S zK9ka6R;LRaKqrKmn8_eZlJhZ5LiW`Oy)4RC-f?WC1!_Hk##NuUdP9Ek^B3TsH$M=c z-y9u@I9{)+P_L>_##aTt#D#fo-?Pr<=XrW@VG)iU|9!)uI<{BZSd_nT{Z-nJfAqtK zW1joTv!DXS)vevqs_L_)QdvYtxg0A`SG1}6{NCK%h8`BRrY40!EBJoewox8JEsTIl zX?1RX(L*Q}RNXOCq_7fqj`MfIh*ox{cdXCsNQD}d6kPJ=7hYeAh_Yv)2IWTn^hR@M zB6=7@xDw7EebK``4=rO7DuU^RFwk~T%0lz;C`+Iak5IHl0XpgwO47gTV=TH_Bg!P` z)OKYr6oEB$YNCpoNPipmC*EsNY|0DS3uOfkh_7Uv(OG*RP?a5%{)T)7Q%5J{q56jJ z2X}Nh4`eQsJWMW^iK?H|m_s5Te5r&QZ0`&OchgDIBY>XAVFVztZA3{WVn#IZIKa2?@Snuf(C8kuNl zprtfUM0uVMm6sD9g`NBV`b^bcSX_j^y!lu7)vrgC+mjY6)esV~`ltShC^~?{`e74h zk|x!v&wl7s6H#;_Xu?d=Xg~KnYtrgbWg^t;e>w@ea)!54GHE&PiMCNhjJ`JB%CY0e zp`)WLsz6<)bfGix`kzn2u~&lIr=a^?56Jd&3*yW(d%g)t5_}V;%k2sCFgJ!LyWwh= z+}!Kq0Pn8rt6P*$z3RNg_IBKhcCI-mH(?oRw_K?R zwcb^OTv^3Av-@`Z+ONZolqYXPUQ!WCIZ7I`KZ(k>+p>h(2LFTQbv~Hm4Yq_c5rxxl z3D{=iDWr&fRhZ3DbI-qOSYBSrf~7^KZB*2a%AI88z6tkp?EJ|7Ip|2dWK%w&z8IOZD@9@gM8s^zxMZW_f>i9*_bmhFF?mGnldUl$8=r*TrGJz)kx z;ePkZad4+%=q*-ilUtHUo(iKDCH~y^{9sRRX`u<20YmB%_5>LnYeKDsid)`|FQS2V zXepHHmzcv){fe`0Xem_CZ}$a`31?h({=);OFKJsB9~Af^`91@vh;YT5IHp zY)weRFvs){Dx-uMuVEGVwF7@B+N%^mk-A?z#Xasa_Z5uM>A~`tf1kiu<@R$6SNWn3 z|Ks)S%X^A{QJxUCNLJ_e>hxg8w^aKD4ji^^G6v6FYsiavAhIx<0s(O`O{FaRtR?-o)K*lHOB#kNK?a=`QRgB(EnC#lo5IW z(+ii!gV8m+@lkg!3eFRlB8(lyC=fKsnywrLqYMs0i9T&ti3JYK{KKl$V5c{% zz-XAW5XHJZVnQ_TDDo@oBa}SE2=ovjJ&+HQAEW1?H^$gJP8MQyc9E}N$I Void + let onClickDelete: () -> Void + let onClickSubstract: () -> Void + + var body: some View { + VStack(spacing: 6.7) { + HStack(spacing: 0) { + Text("옵션 \(index + 1)") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + if index > 1 { + Text("삭제") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "ff5c49")) + .onTapGesture { onClickDelete() } + } + } + + HStack(spacing: 8) { + TextField("옵션을 입력하세요", text: $option.title) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .keyboardType(.default) + .padding(.horizontal, 13.3) + .padding(.vertical, 16.7) + .frame(maxWidth: .infinity) + .background(Color(hex: "222222")) + .cornerRadius(6.7) + + Text("\(option.percentage)%") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .padding(.horizontal, 13.3) + .padding(.vertical, 16.7) + .background(Color(hex: "222222")) + .cornerRadius(6.7) + + Image("btn_minus_round_rect") + .onTapGesture { onClickSubstract() } + + Image("btn_plus_round_rect") + .onTapGesture { onClickPlus() } + } + } + } +} + +struct RouletteSettingsOptionView_Previews: PreviewProvider { + static var previews: some View { + RouletteSettingsOptionView( + option: RouletteOption(title: "옵션1", weight: 1), + index: 2, + onClickPlus: {}, + onClickDelete: {}, + onClickSubstract: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift new file mode 100644 index 0000000..7074633 --- /dev/null +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift @@ -0,0 +1,158 @@ +// +// RouletteSettingsView.swift +// SodaLive +// +// Created by klaus on 2023/12/05. +// + +import SwiftUI + +struct RouletteSettingsView: View { + + @StateObject var keyboardHandler = KeyboardHandler() + @StateObject var viewModel = RouletteSettingsViewModel() + + @Binding var isShowing: Bool + + var body: some View { + GeometryReader { proxy in + BaseView(isLoading: $viewModel.isLoading) { + VStack(spacing: 0) { + DetailNavigationBar(title: "룰렛설정") { + isShowing = false + } + + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 0) { + HStack(spacing: 0) { + Text("룰렛을 활성화 하시겠습니까?") + .font(.custom(Font.bold.rawValue, size: 16)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + Image(viewModel.isActive ? "btn_toggle_on_big" : "btn_toggle_off_big") + .resizable() + .frame(width: 44, height: 27) + .onTapGesture { + viewModel.isActive.toggle() + } + } + + VStack(alignment: .leading, spacing: 13.3) { + Text("룰렛 금액 설정") + .font(.custom(Font.bold.rawValue, size: 16)) + .foregroundColor(Color(hex: "eeeeee")) + + HStack(spacing: 8) { + TextField("룰렛 금액을 입력해 주세요 (최소 5캔)", text: Binding( + get: { + self.viewModel.canText + }, + set: { newValue in + self.viewModel.canText = newValue.filter { "0123456789".contains($0) } + } + )) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .keyboardType(.numberPad) + .padding(.horizontal, 13.3) + .padding(.vertical, 16.7) + .frame(maxWidth: .infinity) + .background(Color(hex: "222222")) + .cornerRadius(6.7) + + Spacer() + + Text("캔") + .font(.custom(Font.bold.rawValue, size: 16.7)) + .foregroundColor(Color(hex: "eeeeee")) + } + } + .padding(.top, 26.7) + + VStack(alignment: .leading, spacing: 21.3) { + Text("룰렛 옵션 설정") + .font(.custom(Font.bold.rawValue, size: 16)) + .foregroundColor(Color(hex: "eeeeee")) + + HStack(spacing: 0) { + Text("※ 룰렛 옵션은 최소 2개,\n최대 6개까지 설정할 수 있습니다.") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "ff5c49")) + + Spacer() + + Image("btn_add") + .onTapGesture { viewModel.addOption() } + } + } + .padding(.top, 26.7) + + LazyVStack(spacing: 21.3) { + ForEach(viewModel.options.indices, id: \.self) { index in + RouletteSettingsOptionView( + option: viewModel.options[index], + index: index, + onClickPlus: { viewModel.plusWeight(index: index) }, + onClickDelete: { viewModel.deleteOption(index: index) }, + onClickSubstract: { viewModel.subtractWeight(index: index) } + ) + } + } + .padding(.top, 21.3) + } + .padding(.horizontal, 13.3) + } + + Spacer() + + HStack(spacing: 13.3) { + Text("미리보기") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "3bb9f1")) + .padding(.vertical, 16) + .frame(maxWidth: .infinity) + .overlay( + RoundedRectangle(cornerRadius: 10) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color(hex: "3bb9f1")) + ) + .onTapGesture { + } + + Text("설정완료") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(.white) + .padding(.vertical, 16) + .frame(maxWidth: .infinity) + .background(Color(hex: "3bb9f1")) + .cornerRadius(10) + .onTapGesture { + } + } + .padding(13.3) + .background(Color(hex: "222222")) + .cornerRadius(16.7, corners: [.topLeft, .topRight]) + + if proxy.safeAreaInsets.bottom > 0 { + Rectangle() + .foregroundColor(Color(hex: "222222")) + .frame(width: screenSize().width, height: 15.3) + } + } + } + .onAppear { + viewModel.getRoulette(creatorId: UserDefaults.int(forKey: .userId)) + } + } + } +} + +struct RouletteSettingsView_Previews: PreviewProvider { + static var previews: some View { + RouletteSettingsView(isShowing: .constant(true)) + } +} diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift new file mode 100644 index 0000000..04bdef7 --- /dev/null +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift @@ -0,0 +1,121 @@ +// +// RouletteSettingsViewModel.swift +// SodaLive +// +// Created by klaus on 2023/12/05. +// + +import Foundation +import Combine + +final class RouletteSettingsViewModel: ObservableObject { + private let repository = RouletteRepository() + private var subscription = Set() + + @Published var isLoading = false + + @Published var errorMessage = "" + @Published var isShowErrorPopup = false + + @Published var canText = ""{ + didSet { + can = Int(canText) ?? 0 + } + } + @Published var isActive = false + @Published var options = [RouletteOption]() + + var can = 5 + + func plusWeight(index: Int) { + options[index].weight += 1 + recalculatePercentages() + } + + func subtractWeight(index: Int) { + if options[index].weight > 1 { + options[index].weight -= 1 + recalculatePercentages() + } + } + + func addOption() { + if (options.count >= 6) { + return + } + options.append(RouletteOption(title: "", weight: 1)) + recalculatePercentages() + } + + func deleteOption(index: Int) { + options.remove(at: index) + recalculatePercentages() + } + + private func recalculatePercentages() { + let options = options + + var totalWeight = 0 + for option in options { + totalWeight += option.weight + } + + guard totalWeight > 0 else { return } + + for i in 0...self, from: responseData) + + if let data = decoded.data, decoded.success { + self.isActive = data.isActive + self.canText = String(data.can) + if !data.items.isEmpty { + let options = data.items.map { + RouletteOption(title: $0.title, weight: $0.weight) + } + removeAllAndAddOptions(options: options) + recalculatePercentages() + } else { + self.addOption() + self.addOption() + } + } else { + self.isActive = false + self.canText = "5" + self.addOption() + self.addOption() + } + } catch { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.isShowErrorPopup = true + } + } + .store(in: &subscription) + } +} diff --git a/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift b/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift new file mode 100644 index 0000000..b159fde --- /dev/null +++ b/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift @@ -0,0 +1,17 @@ +// +// GetRouletteResponse.swift +// SodaLive +// +// Created by klaus on 2023/12/06. +// + +struct GetRouletteResponse: Decodable { + let can: Int + let isActive: Bool + let items: [RouletteItem] +} + +struct RouletteItem: Decodable { + let title: String + let weight: Int +} diff --git a/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift b/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift new file mode 100644 index 0000000..14e9ec3 --- /dev/null +++ b/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift @@ -0,0 +1,51 @@ +// +// RouletteApi.swift +// SodaLive +// +// Created by klaus on 2023/12/06. +// + +import Foundation +import Moya + +enum RouletteApi { + case getRoulette(creatorId: Int) +} + +extension RouletteApi: TargetType { + var baseURL: URL { + return URL(string: BASE_URL)! + } + + var path: String { + switch self { + case .getRoulette: + return "/roulette" + } + } + + var method: Moya.Method { + switch self { + case .getRoulette: + return .get + } + } + + var task: Moya.Task { + switch self { + case .getRoulette(let creatorId): + let parameters = [ + "creatorId": creatorId + ] as [String : Any] + + return .requestParameters( + parameters: parameters, + encoding: URLEncoding.queryString + ) + } + } + + var headers: [String : String]? { + return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"] + } +} diff --git a/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift b/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift new file mode 100644 index 0000000..ea7caec --- /dev/null +++ b/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift @@ -0,0 +1,20 @@ +// +// RouletteRepository.swift +// SodaLive +// +// Created by klaus on 2023/12/06. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +final class RouletteRepository { + private let api = MoyaProvider() + + func getRoulette(creatorId: Int) -> AnyPublisher { + return api.requestPublisher(.getRoulette(creatorId: creatorId)) + } +} +