From 01833fbc1fba9844596eac584da72517ef10ea6f Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 18 Jan 2024 17:32:14 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=20UI=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 --- .../btn_plus_round.png | Bin 1038 -> 2805 bytes .../btn_select_checked.png | Bin 544 -> 1553 bytes .../ic_crown.imageset/ic_crown.png | Bin 634 -> 2207 bytes .../ic_kick_out.imageset/ic_kick_out.png | Bin 2522 -> 2827 bytes .../ic_notice_triangle.imageset/Contents.json | 21 + .../ic_notice_triangle.png | Bin 0 -> 419 bytes .../ic_request_speak.png | Bin 5076 -> 6196 bytes .../ic_speaker_on.imageset/ic_speaker_on.png | Bin 5950 -> 5971 bytes .../Detail/LiveRoomDonationDialogView.swift | 38 +- .../Profile/UserProfileDonationView.swift | 6 +- .../Live/Room/Chat/LiveRoomChatItemView.swift | 4 +- .../Chat/LiveRoomDonationChatItemView.swift | 14 +- .../Room/Chat/LiveRoomJoinChatItemView.swift | 8 +- .../LiveRoomDonationRankingDialog.swift | 12 +- .../LiveRoomDonationRankingTotalCanView.swift | 8 +- .../Dialog/LiveRoomNoChattingDialogView.swift | 10 +- .../Room/Dialog/LiveRoomProfileDialog.swift | 6 +- .../Dialog/LiveRoomProfileItemTitleView.swift | 34 +- .../Dialog/LiveRoomProfilesDialogView.swift | 8 +- .../LiveRoomUserProfileDialogView.swift | 30 +- SodaLive/Sources/Live/Room/LiveRoomView.swift | 1033 ----------------- .../Sources/Live/Room/LiveRoomViewModel.swift | 20 +- .../Button/LiveRoomNewChatView.swift | 40 + .../LiveRoomOverlayStrokeImageButton.swift | 43 + .../LiveRoomOverlayStrokeTextButton.swift | 45 + ...iveRoomOverlayStrokeTextToggleButton.swift | 59 + .../Button/LiveRoomRightBottomButton.swift | 33 + .../Room/V2/Component/Text/TextView.swift | 31 + .../V2/Component/View/LiveRoomChatView.swift | 67 ++ .../View/LiveRoomInfoCreatorView.swift | 103 ++ .../View/LiveRoomInfoGuestView.swift | 190 +++ .../Component/View/LiveRoomInfoHostView.swift | 217 ++++ .../View/LiveRoomInfoSpeakerView.swift | 62 + .../View/LiveRoomInputChatView.swift | 55 + .../Sources/Live/Room/V2/LiveRoomViewV2.swift | 668 +++++++++++ SodaLive/Sources/Main/Home/HomeView.swift | 2 +- SodaLive/Sources/UI/Theme/Color.swift | 31 + 37 files changed, 1763 insertions(+), 1135 deletions(-) create mode 100644 SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/ic_notice_triangle.png delete mode 100644 SodaLive/Sources/Live/Room/LiveRoomView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoCreatorView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoSpeakerView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift create mode 100644 SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift create mode 100644 SodaLive/Sources/UI/Theme/Color.swift diff --git a/SodaLive/Resources/Assets.xcassets/btn_plus_round.imageset/btn_plus_round.png b/SodaLive/Resources/Assets.xcassets/btn_plus_round.imageset/btn_plus_round.png index d66ac2782d56ce8c8660543972e604bc9414b5f4..a14e131aaa4260c4aabb1db9ad2e445a5bb4b61a 100644 GIT binary patch literal 2805 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 Hu0mjfC`3*T literal 1038 zcmV+p1o8WcP)Px#Hc(7dMVWB_nQ;G_asQcc|A%7Yd0EbkW$Z_AOq6T- zkZALoaQ~Tb|Cw?Bn{xk}a{r=z|DAUKnQ;FvS+#Ni000qmQchDc>vfzqIyC??GqucF zI7kc{+r)*{000A7Nklw3c=2!>UxYdz#}|2I1mlPHOx$yR>#dAhwtkxxMJ z)3zyiucCP$rj9=s4VnihrLz{IS-X4&KRriI%1H};5QPmMtaNI6f*d0cyr^055UJx$ z zibsMQ#7!F1ky05awtFQ!s7{#z}EFspW!{dSxN--t@?k$C zV?rX?ppxh8C7nKtY+!LaGpoquD?=pYDsBLjY?$RgtFmE8*;pm`n&gGJnX0%MTHMx) z;u@bnNN`xMv&v>ZzbMkBdnyh7cAagdfsp@q>23wgwaj{48ti2jTMTY)ZpC1$fH{gm zu4LVIGnK5ZGRq$&D_2XG3!kR1pi+nGYqFMe^ZoW@U!xz?NlR~vpSwoi8 zER~|&N)ZiTp-aiQCMDp8k7!G8wNmG{lvw(WhVR-u$Fv;&(w0_Q{Q4lGyLqk-BI@Ik z&K92-m&6dwPX`gsQllqld;GlmIK%w`89RVr2V~zE<9%b;xSY=$BT>;a61_LLRb7MI zZ;Y4Ij<$#3{>~8m8>9NhgMxVUppd@%=VuT8ab|pueDZ|!3o%@NMK`@xGynhq07*qo IM6N<$g2{Q@Q2+n{ diff --git a/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png index ee6d977913f11d5f4c7772c1afeb61770e2f4c27..167a0fbfab3848083f53da686d1148295d32e91f 100644 GIT binary patch delta 1547 zcmV+m2K4!$1d$Ao8Gi-<0027t*>V5?010qNS#tmY3ljhU3ljkVnw%H_0004VQb$4n zuFf3k00004XF*Lt006O%3;baP000H1NkltysCDm4zJ3u)5l z?{ox5l6RyCmtecKBg#RnB{_(tRB3Lv7t26a2y!+!mb@{cgx{Ttp3$HtpnM?YTh-o9 zM?wjkpnry;yg=^UBm}Uyu=IS&q$(%EIls8=e2fbdN}gPnB$Tq%hOO?NQj1We)CCYa z4pl&11;XV04S2Xl;1J>r>;=idtP)0qGf<=)tWt3K?HcHNy$X6>Jpf~^wQ!`)ajzCP zYQXB{sn{>08;X!a+pU@P@qeWJRiSClJF$SBc^gD_lw}WArSG7MmN54Yxwl# zN`J0q0N0k*-P{}F*3f#EAJ2W8ysz9=_|4(2E$CKE$n@^_w80zWd+miHe=efk^quI4@ z&q11={h*;;-UfEJnS+EX1RgW_z<;}p_}4YDHoW<+0)OzvhyLK^B|=R zTy{$F5+)s=bu%p-E;BSM(^9J`t1jQ1ZqUVQa>GEA_w< zgYSZPG2IfTv;zRMUBM9~?diQakMOvp9-Td1|4q7`wj&;a10Vrx9!ih-+kYLwe!HEv z!jfZGRO)uF8$~;fng&^o_i?5JGLC79G!GDziuB*vEIr(J5Km4Z(onqBThjD7p74Il zlf|WbIdL;O%;{&J)9?9+6`%sEFqrK_A&xIxR_TFK1N_A%C?t|AXgxUF5gc{)H5XWG zLI5tX_*uw*X>$feVUwuG&3_{RIbEzQWHUj1y}jJYZ*fZ$iH34{y2ZQ4cn}8wG34cW zxA8=D2Tw~ysyZ5~TWRWcDZe`~9h02lfwxgFpHlO!8p;Battx1?mQ*M_DiiO7z;n^q xxc4|NzKAvZLAan5ECfg|87na3*Q0pP0GlDV2LY2okpKVy07*qoLzW|C(_BnQ;G_a{rri|Cw?BnsNV{aQ~fk|C(|CnQ{M_aR2}R|K{BP zxSRi}hX0>=|Mv3#$g=;{#Q&_%%b@@O01R|ePE!C$rr&fwpnvqm6SGF(QGC+?00C!7 zL_t(Y$EBCql7k=+MO_GsUPPn+|E(dJI5Q|qL*JKFEKUJUuU!|FnT6D&@*1@iv+2k( zLMsGPTG05%gs7%R8P!Ni=W#P2t08mvUZEh>r&s}kM4Xi%Nye_LL6p8T0)d!GA;dk8QE` zxn4bmX0Fw<)*cs=(Hvxb+8gX0Wv*buV`C;=`DEQSJY?&>@5t26thd-0EaHJ_4P|&+LORQOcz;Bi-m|PXm`0K=u>PqeQuN7Y z(zif@{{9@f>^&nzmslSdv5;Iy>^paBr1w5b`7;`mKlENKSn&k76xmM#Or&17uY}ag zOZu#G!z*;G63Z*@thR#J(C}I*UK7V_A6bnnvt{Nr>%1|6H=r;_Bj!+6F%G@}?lgvJ U7@V?o01E&B07*qoM6N<$f`q;TX#fBK diff --git a/SodaLive/Resources/Assets.xcassets/ic_crown.imageset/ic_crown.png b/SodaLive/Resources/Assets.xcassets/ic_crown.imageset/ic_crown.png index 003ab3cf837f642c2faccb5536e788f16db6ba67..ae5bce02f366efb2dc00b3373a85a245deabc215 100644 GIT binary patch literal 2207 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 Y1ElxH(LneG9smFU07*qoM6N<$g1~7;=Kufz delta 2463 zcmV;Q31Ie%7TOb#ReuR@Nkl?SV}-i z0S;Tb{+q}a{;+jCZAf4mVtmP8Aw@;RoJl}fTXRWd2Or+dynKe|-{OiVO3Pb_dU6M^ zZA9u!ZZNo4%oNkj78c!f3oAluoe-ggv}IpDg(|z7`|2pq zHF62~yrpS%Ek4Fac9Ay9s)PgAirIF?BKJ2V^<>g}#|p?s%C92dYg1;^(spstNCI== zxe=0vvV&BIILEg{9*ryIMhZ9}UX0e}J0?*;jz2fqPv`52fXWV&vF;1j=O*aL%P)5F08iDq z)hre8W9FCUV8Brzp5`1PA49l*FjuztJjLS}Eq|UB2{;Wp^sEqnM!JhS@sG5?CJ#W_ zHxh83o=LCl)8?|B9TwZq2sn;TeiCED1iw63h6z64D_jC00q0pVSf`FbA1oWIBpyvbuJtqk0 zIe$Sw&j|v0P7u&@f`FbA1oWI`@f5Bkz=YVxm>r`d$lXhkx5!U8Qh$>M=Dic}ntQi4 zuQ5xD66Oajo!is(OS61^$NKpf0`qg<-x7JUNT*k?1WX-G^@Q`Zaa~@AIPHH&YIn@_ zIJdSfc1>(i==gdr@>5yY%Y!#{AgqcGH-D{Z-UD&k5OHVJab=bz{7p(ET`jgKybzEg z=oyPRiU>BONq5+UB;(oz)RM-xNlAicv4 z+^I?!%4(tkoDB@7NQ?#BBn9`T|TdK;yw%vk5(#vkFvw?`fK zSv=x}fUFA{vrVR~53P9XqmKJ79`U9h$eXwssnwpT6XOY|^Z4U&;xViv{`nQ3#UtL$ z$g?=a;B$0t;&wBD_mNi2tdJnOA=^NjnM z)7~P*_y@Z@>61u}9ZI!A$A4lh(My~c&{|t68XFxCF}vQ(QTBJAZ|lx&Ew4W0y)B#; z@L)R$YjY0|_b)O#oaDi-8*So!@s};#!9V)+Lln}xK`HXV{9-4*)t?mVyS6s+j)~tL3>E*a}qCVoH1>WO63A%)(?tjC8HST;!B041? z{S+YHH?uB4%8B}rM07?#+b3&hMm6S1BH~xux=7vX1TI)WrN;TJT zLO?q+P{pFzd)Wx^KH`c6u1iIG@UpSwvlUh@AgynySU;~QDzkg0cer4IjRiGaMxv_y z0u`ci0rk~}%2iU&ZhvRv6O{{y;`5<6KyAK#&$;155h~Tgdhk!;AwfV(&yl}lT3is& zIiEY92<~N`p3(Qt@e|IubUWoS9GudQK3~ zbAo`L69n{}AfV?20X?TL;9+Z;Zd1S&C}4jtIth43U%>Mqpnn%UGhw0|_G#P~5EEh# zB;X4T{-QfKH$hGU;3zFC5^x?n$eXSnc(aTrCv*eip5zj6o+aZe`ByRi_$MqvyF9?f zMbAP@)E5#9WcaX}b?9eeBc>?yCiH$Q)P?Q=p%l{p_!wGs(pWbAp|mtE zXU+Htgf4|cWIFFHF}uy|rN{aL+W2BGk?LrwzIYz2&?Q*+?brvMY$T~`vk`WI(L3vG z(mDttRt`h0EGgNtb>C^$`pA1RsbG0DQ$~kiz4!=kIDgI?x++jEwk^U9E`LCN7iw(` z#$tPKK5#Qqkv6T;x;Fa+t%OF+;>n1Y#4}~9b66m~k){Ppr0U%4DYjwuPlF{ch5_*! zxa-HCMCvcY&K)@;f7^(5ET-?l3$28P&HVY?v^ZC@;@vV@3}viIanHRAb0JzHr=9lj zhKL&{27hZTpCmRMx^d{Gw~^OV@y5k?%ca5>Exm93$mvJN4TkrYQKT~bV-T`9X_3k9 zo9zK`s4V(Vq_!u$H}~Dghpg93NRFhFkH@Fl$| z#$=vrOm)|7IF23LCsFnSw8*b(>9mx>IGwF3gMX;Qu@=XG?P~+$&o9{*pC;J)Om}#g zkYc8!P9WfG2>3?KdW~2rWOG8q-g%Q+7}p!o2Osz5eoZ4cfgSpj2HFHp-^~+ke~>~Z zLLpf<+U-pnWnA;7!1XLhN1O>N5{S06n{BPKX_sa&QQU@9iwj>S?t#crE#|Ma*?p#4 d))P z{lEMv@+8Z$_c)Hjx~`ApfglgkG!2zfdw$hz+h$c&oyieN9!M#__(=G94uas5JaA6T zX%kL+7=6-0h?lY~|HuL7#Ju$P?vv13JEvAI!+D;6@bPN+l|)f=X`1GrK;Wux6?YdZ zTo^gsF4~a*;Hr7)w`FO^6VLn*2(-9!mX9}JbXOF`4wzf7dgN?E7zf1d)x;xbQ6RUi zi4D%8th#MYY;YE3)$K)(EzY7?-8NPYIE%9Awy|o!S(HV$XNw)2MKQWPsoKL?l$qO; z#2(I~%-kLlKj19N#O)#R1J0sM-0nTzz*&^RZLy)VD81WqLuXNXx3B+nb^(Y65<>t0 N002ovPDHLkV1lf|uK@r6 literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/ic_request_speak.imageset/ic_request_speak.png b/SodaLive/Resources/Assets.xcassets/ic_request_speak.imageset/ic_request_speak.png index 52a3b1347bbfe337b37c4df7ec8e4a95a52a4447..a7d8c46befdfcbf27eef0093f5cb574037624e73 100644 GIT binary patch literal 6196 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 diff --git a/SodaLive/Resources/Assets.xcassets/ic_speaker_on.imageset/ic_speaker_on.png b/SodaLive/Resources/Assets.xcassets/ic_speaker_on.imageset/ic_speaker_on.png index be48c2d04961f8ff8d8b196468b4340727b4c1b1..c3de9c67eed1c774c4ac3a989fe3358249199f95 100644 GIT binary patch delta 5938 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 diff --git a/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift b/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift index 04892b1..b957f71 100644 --- a/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift +++ b/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift @@ -44,7 +44,7 @@ struct LiveRoomDonationDialogView: View { Text("후원하기") .font(.custom(Font.bold.rawValue, size: 18.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Spacer() @@ -55,7 +55,7 @@ struct LiveRoomDonationDialogView: View { Text("\(can)") .font(.custom(Font.bold.rawValue, size: 18.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Image("ic_forward") } @@ -69,15 +69,15 @@ struct LiveRoomDonationDialogView: View { Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090")) + .foregroundColor(Color.gray90) .padding(.top, 16) TextField("몇 캔을 후원할까요?", text: $donationCan) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .padding(13.3) .keyboardType(.numberPad) - .background(Color(hex: "303030")) + .background(Color.gray30) .cornerRadius(6.7) .padding(.horizontal, 20) .padding(.top, 16) @@ -88,7 +88,7 @@ struct LiveRoomDonationDialogView: View { .foregroundColor(.white) .padding(.vertical, 12.7) .frame(width: 74) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(6.7) .onTapGesture { if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty, @@ -106,7 +106,7 @@ struct LiveRoomDonationDialogView: View { .foregroundColor(.white) .padding(.vertical, 12.7) .frame(width: 74) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(6.7) .onTapGesture { if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty, let can = Int(donationCan) { @@ -122,7 +122,7 @@ struct LiveRoomDonationDialogView: View { .foregroundColor(.white) .padding(.vertical, 12.7) .frame(width: 74) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(6.7) .onTapGesture { if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty, @@ -140,7 +140,7 @@ struct LiveRoomDonationDialogView: View { .foregroundColor(.white) .padding(.vertical, 12.7) .frame(width: 74) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(6.7) .onTapGesture { if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty, @@ -156,7 +156,7 @@ struct LiveRoomDonationDialogView: View { Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090")) + .foregroundColor(Color.gray90) .padding(.vertical, 18.7) .padding(.horizontal, 20) @@ -169,14 +169,14 @@ struct LiveRoomDonationDialogView: View { .clipShape(Circle()) .overlay( Circle() - .stroke(Color(hex: "bbbbbb"), lineWidth: 1) + .stroke(Color.graybb, lineWidth: 1) ) TextField("함께 보낼 메시지 입력(최대 50자)", text: $donationMessage) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .padding(13.3) - .background(Color(hex: "303030")) + .background(Color.gray30) .cornerRadius(6.7) .onReceive(Just(donationMessage)) { _ in limitText() @@ -187,15 +187,15 @@ struct LiveRoomDonationDialogView: View { HStack(spacing: 13.3) { Text("취소") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .padding(.vertical, 16) .frame(width: (screenSize().width - 53.3) / 3) - .background(Color(hex: "9970ff").opacity(0.2)) + .background(Color.button.opacity(0.2)) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .strokeBorder() - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) ) .onTapGesture { isShowing = false @@ -206,7 +206,7 @@ struct LiveRoomDonationDialogView: View { .foregroundColor(.white) .padding(.vertical, 16) .frame(width: (screenSize().width - 53.3) * 2 / 3) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(10) .onTapGesture { if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty, @@ -224,7 +224,7 @@ struct LiveRoomDonationDialogView: View { } .padding(.top, 21.3) .padding(.bottom, 16) - .background(Color(hex: "222222")) + .background(Color.gray22) .cornerRadius(20, corners: [.topLeft, .topRight]) } .popup(isPresented: $isShowErrorPopup, type: .toast, position: .bottom, autohideIn: 1.3) { @@ -234,7 +234,7 @@ struct LiveRoomDonationDialogView: View { .padding(.vertical, 13.3) .frame(width: screenSize().width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) - .background(Color(hex: "9970ff")) + .background(Color.button) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .cornerRadius(20) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift index 69f66f3..3a48f27 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift @@ -25,13 +25,13 @@ struct UserProfileDonationView: View { HStack(spacing: 0) { Text("후원랭킹") .font(.custom(Font.bold.rawValue, size: 16.7)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Spacer() Text("전체보기") .font(.custom(Font.light.rawValue, size: 11.3)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) .onTapGesture { AppState.shared.setAppStep(step: .userProfileDonationAll(userId: userId)) } @@ -71,7 +71,7 @@ struct UserProfileDonationView: View { Text(item.nickname) .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(.grayee) .frame(width: 63) .lineLimit(1) } diff --git a/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift b/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift index 7928737..c7d706c 100644 --- a/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift +++ b/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift @@ -23,7 +23,7 @@ struct LiveRoomChatItemView: View { .clipShape(Circle()) case -1: - Color(hex: "6f3dec") + Color.button .frame(width: 33.3, height: 33.3, alignment: .top) .clipShape(Circle()) @@ -119,7 +119,7 @@ struct LiveRoomChatItemView: View { .padding(.vertical, 5.3) .background( UserDefaults.int(forKey: .userId) == chatMessage.userId ? - Color(hex: "9970ff").opacity(0.6) : + Color.button.opacity(0.5) : Color.black.opacity(0.6) ) .cornerRadius(3.3) diff --git a/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift b/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift index d67feab..b228a76 100644 --- a/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift +++ b/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift @@ -58,13 +58,13 @@ struct LiveRoomDonationChatItemView: View { .padding(13) .frame(width: screenSize().width - 86, alignment: .leading) .background( - chatMessage.can >= 10000 ? Color(hex: "c25264") : - chatMessage.can >= 5000 ? Color(hex: "d85e37").opacity(0.9) : - chatMessage.can >= 1000 ? Color(hex: "d38c38").opacity(0.9) : - chatMessage.can >= 500 ? Color(hex: "59548f").opacity(0.9) : - chatMessage.can >= 100 ? Color(hex: "4d6aa4").opacity(0.9) : - chatMessage.can >= 50 ? Color(hex: "2d7390").opacity(0.9) : - Color(hex: "548f7d").opacity(0.9) + chatMessage.can >= 10000 ? Color(hex: "c25264").opacity(0.8) : + chatMessage.can >= 5000 ? Color(hex: "d85e37").opacity(0.8) : + chatMessage.can >= 1000 ? Color(hex: "d38c38").opacity(0.8) : + chatMessage.can >= 500 ? Color(hex: "59548f").opacity(0.8) : + chatMessage.can >= 100 ? Color(hex: "4d6aa4").opacity(0.8) : + chatMessage.can >= 50 ? Color(hex: "2d7390").opacity(0.8) : + Color(hex: "548f7d").opacity(0.8) ) .cornerRadius(10) .padding(.leading, 20) diff --git a/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift b/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift index 4301635..ac1e19a 100644 --- a/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift +++ b/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift @@ -15,19 +15,19 @@ struct LiveRoomJoinChatItemView: View { HStack(spacing: 0) { Text("'") .font(.system(size: 12)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Text(chatMessage.nickname) .font(.system(size: 12, weight: .bold)) - .foregroundColor(Color(hex: "ffdc00")) + .foregroundColor(Color.mainYellow) Text("'님이 입장하셨습니다.") .font(.system(size: 12)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) } .padding(.vertical, 6.7) .frame(width: screenSize().width - 86) - .background(Color(hex: "3d2a6c")) + .background(Color.button.opacity(0.5)) .cornerRadius(4.7) .padding(.leading, 20) } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift index 76abc18..650fef8 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift @@ -20,7 +20,7 @@ struct LiveRoomDonationRankingDialog: View { HStack(spacing: 0) { Text("현재 라이브 후원랭킹") .font(.custom(Font.bold.rawValue, size: 14.7)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Spacer() @@ -35,16 +35,16 @@ struct LiveRoomDonationRankingDialog: View { HStack(spacing: 0) { Text("전체") .font(.custom(Font.medium.rawValue, size: 14.7)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Text("\(donationStatus.totalCount)") .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .padding(.leading, 6.7) Text("명") .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) Spacer() } @@ -66,7 +66,7 @@ struct LiveRoomDonationRankingDialog: View { } } .padding(20) - .background(Color(hex: "222222")) + .background(Color.gray22) .cornerRadius(8) if viewModel.isLoading { @@ -80,7 +80,7 @@ struct LiveRoomDonationRankingDialog: View { .padding(.vertical, 13.3) .frame(width: screenSize().width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) - .background(Color(hex: "9970ff")) + .background(Color.button) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .cornerRadius(20) diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift index 43073a3..82312ea 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift @@ -15,22 +15,22 @@ struct LiveRoomDonationRankingTotalCanView: View { HStack(alignment: .center, spacing: 0) { Text("합계") .font(.custom(Font.bold.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "d2d2d2")) + .foregroundColor(Color.grayd2) Spacer() Text("\(totalCan)") .font(.custom(Font.medium.rawValue, size: 16)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) Text("캔") .font(.custom(Font.medium.rawValue, size: 10.7)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) .padding(.leading, 4) } .padding(.horizontal, 18.7) .padding(.vertical, 10.7) - .background(Color(hex: "13181b")) + .background(Color.bg) .cornerRadius(8) } } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift index 92a10f3..24d8a2a 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift @@ -43,14 +43,14 @@ struct LiveRoomNoChattingDialogView: View { HStack(spacing: 13.3) { Text("취소") .font(.custom(Font.bold.rawValue, size: 15.3)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .padding(.vertical, 16) .frame(width: (screenSize().width - 80) / 2) - .background(Color(hex: "9970ff").opacity(0.13)) + .background(Color.button.opacity(0.13)) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) - .stroke(Color(hex: "9970ff"), lineWidth: 1) + .stroke(Color.button, lineWidth: 1) ) .onTapGesture { cancelAction() } @@ -59,7 +59,7 @@ struct LiveRoomNoChattingDialogView: View { .foregroundColor(Color(hex: "ffffff")) .padding(.vertical, 16) .frame(width: (screenSize().width - 80) / 2) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(8) .onTapGesture { confirmAction() } } @@ -67,7 +67,7 @@ struct LiveRoomNoChattingDialogView: View { .padding(.top, 40) .padding(.bottom, 16.7) .padding(.horizontal, 16.7) - .background(Color(hex: "222222")) + .background(Color.gray22) .cornerRadius(10) } } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift index fc60a40..e63f8e1 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift @@ -46,7 +46,7 @@ struct LiveRoomProfileDialog: View { if profileInfo.role == .LISTENER, let onClickInviteSpeaker = onClickInviteSpeaker { Text("스피커로 초대") .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(.button) .padding(.horizontal, 15.4) .padding(.vertical, 8.3) .background(Color.white) @@ -61,7 +61,7 @@ struct LiveRoomProfileDialog: View { let onClickChangeListener = onClickChangeListener { Text("리스너로 변경") .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(.button) .padding(.horizontal, 15.4) .padding(.vertical, 8.3) .background(Color.white) @@ -79,7 +79,7 @@ struct LiveRoomProfileDialog: View { } .padding(20) .frame(width: screenSize().width - 53.4) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(16.7) } } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift index 77a3e90..38bfaa0 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift @@ -18,19 +18,19 @@ struct LiveRoomProfileItemTitleView: View { HStack(spacing: 0) { Text(title) .font(.custom(Font.bold.rawValue, size: 13)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) if let count = count { Text("\(count)") .font(.custom(Font.medium.rawValue, size: 13)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .padding(.leading, 6.7) } if let totalCount = totalCount { Text("/\(totalCount > 4 ? 4 : totalCount - 1)") .font(.custom(Font.medium.rawValue, size: 13)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) } Spacer() @@ -59,14 +59,14 @@ struct LiveRoomProfileItemMasterView: View { Text(nickname) .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .padding(.leading, 4) } .padding(.horizontal, 16.7) Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.3)) + .foregroundColor(Color.gray90.opacity(0.3)) } } } @@ -101,7 +101,7 @@ struct LiveRoomProfileItemUserView: View { Text(nickname) .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .lineLimit(2) .multilineTextAlignment(.leading) .padding(.leading, 4) @@ -109,7 +109,7 @@ struct LiveRoomProfileItemUserView: View { } else { Text(nickname) .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .lineLimit(2) .multilineTextAlignment(.leading) .padding(.horizontal, 10) @@ -120,14 +120,14 @@ struct LiveRoomProfileItemUserView: View { if role == .LISTENER && isStaff { Text("스피커로 초대") .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "ffffff")) + .foregroundColor(.white) .padding(.horizontal, 5.5) .padding(.vertical, 12) - .background(Color(hex: "9970ff").opacity(0.3)) + .background(Color.button.opacity(0.3)) .cornerRadius(6.7) .overlay( RoundedRectangle(cornerRadius: 6.7) - .stroke(Color(hex: "9970ff"), lineWidth: 1) + .stroke(Color.button, lineWidth: 1) ) .onTapGesture { onClickInviteSpeaker(userId) @@ -137,10 +137,10 @@ struct LiveRoomProfileItemUserView: View { if role == .SPEAKER && (userId == UserDefaults.int(forKey: .userId) || isStaff) { Text("리스너로 변경") .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "ffffff")) + .foregroundColor(.white) .padding(.horizontal, 5.5) .padding(.vertical, 12) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(6.7) .onTapGesture { onClickChangeListener(userId) @@ -150,14 +150,14 @@ struct LiveRoomProfileItemUserView: View { if role != .MANAGER && creatorId == UserDefaults.int(forKey: .userId) { Text("채금") .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "ffffff")) + .foregroundColor(.white) .padding(.horizontal, 5.5) .padding(.vertical, 12) - .background(Color(hex: "9970ff").opacity(0.3)) + .background(Color.button.opacity(0.3)) .cornerRadius(6.7) .overlay( RoundedRectangle(cornerRadius: 6.7) - .stroke(Color(hex: "9970ff"), lineWidth: 1) + .stroke(Color.button, lineWidth: 1) ) .cornerRadius(6.7) .padding(.leading, 10) @@ -177,7 +177,7 @@ struct LiveRoomProfileItemUserView: View { Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.3)) + .foregroundColor(Color.gray90.opacity(0.3)) } .padding(.horizontal, 16.7) } @@ -200,7 +200,7 @@ struct LiveRoomProfileRequestSpeakerView: View { .padding(.vertical, 8) .overlay( RoundedRectangle(cornerRadius: 5.3) - .stroke(Color(hex: "909090"), lineWidth: 1) + .stroke(Color.gray90, lineWidth: 1) ) .onTapGesture { onClickRequestSpeaker() diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift index c871879..c958054 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift @@ -175,16 +175,16 @@ struct LiveRoomProfilesDialogView: View { HStack(spacing: 0) { Text("참여자") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Text("\(roomInfo.participantsCount)") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.button) .padding(.leading, 6.7) Text("/\(roomInfo.totalAvailableParticipantsCount)") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) Spacer() @@ -204,7 +204,7 @@ struct LiveRoomProfilesDialogView: View { } .padding(.vertical, 26.7) .padding(.horizontal, 13.3) - .background(Color(hex: "222222").edgesIgnoringSafeArea(.all)) + .background(Color.gray22.edgesIgnoringSafeArea(.all)) .cornerRadius(16.7) if viewModel.isShowPopup { diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift index 517c0c7..5b051ae 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift @@ -31,7 +31,7 @@ struct LiveRoomUserProfileDialogView: View { HStack(spacing: 0) { Text("프로필") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Spacer() @@ -46,14 +46,14 @@ struct LiveRoomUserProfileDialogView: View { HStack(spacing: 8) { Text(userProfile.nickname) .font(.custom(Font.bold.rawValue, size: 18.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Text(userProfile.gender) .font(.custom(Font.medium.rawValue, size: 11.3)) - .foregroundColor(Color(hex: "ffffff")) + .foregroundColor(.white) .padding(.horizontal, 5.3) .padding(.vertical, 3) - .background(Color(hex: "555555")) + .background(Color.gray55) .cornerRadius(23.3) if let isFollowing = userProfile.isFollowing { @@ -92,14 +92,14 @@ struct LiveRoomUserProfileDialogView: View { if let isSpeaker = userProfile.isSpeaker { Text(isSpeaker ? "리스너 변경" : "스피커 초대") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .frame(maxWidth: .infinity) .padding(.vertical, 13) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) ) .onTapGesture { if isSpeaker { @@ -115,14 +115,14 @@ struct LiveRoomUserProfileDialogView: View { if let isManager = userProfile.isManager { Text(isManager ? "스탭 해제" : "스탭 지정") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .frame(maxWidth: .infinity) .padding(.vertical, 13) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) ) .onTapGesture { if isManager { @@ -139,14 +139,14 @@ struct LiveRoomUserProfileDialogView: View { (userProfile.isSpeaker != nil && userProfile.isManager != nil) { Text("내보내기") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .frame(maxWidth: .infinity) .padding(.vertical, 13) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) ) .onTapGesture { viewModel.kickOutId = userProfile.userId @@ -160,14 +160,14 @@ struct LiveRoomUserProfileDialogView: View { if let _ = userProfile.isManager { Text("3분간 채팅금지") .font(.custom(Font.bold.rawValue, size: 15)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .frame(maxWidth: .infinity) .padding(.vertical, 13) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) ) .onTapGesture { onClickNoChatting(userProfile.userId, userProfile.nickname, userProfile.profileUrl) } .padding(.top, 21.3) @@ -176,7 +176,7 @@ struct LiveRoomUserProfileDialogView: View { if let _ = userProfile.isFollowing, !userProfile.tags.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(userProfile.tags) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) .lineSpacing(3) .padding(.top, 21.3) } @@ -184,7 +184,7 @@ struct LiveRoomUserProfileDialogView: View { if let _ = userProfile.isFollowing, !userProfile.introduce.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(userProfile.introduce) .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "909090")) + .foregroundColor(Color.button) .lineLimit(introduceLineLimit) .lineSpacing(3) .fixedSize(horizontal: false, vertical: true) @@ -203,7 +203,7 @@ struct LiveRoomUserProfileDialogView: View { .padding(.horizontal, 13.3) .padding(.top, 13.3) .padding(.bottom, 20) - .background(Color(hex: "222222")) + .background(Color.gray22) .cornerRadius(8) if viewModel.isShowKickOutPopup { diff --git a/SodaLive/Sources/Live/Room/LiveRoomView.swift b/SodaLive/Sources/Live/Room/LiveRoomView.swift deleted file mode 100644 index d832c1b..0000000 --- a/SodaLive/Sources/Live/Room/LiveRoomView.swift +++ /dev/null @@ -1,1033 +0,0 @@ -// -// LiveRoomView.swift -// SodaLive -// -// Created by klaus on 2023/08/14. -// - -import SwiftUI -import Kingfisher -import PopupView - -struct LiveRoomView: View { - @State private var isShowingNewChat = false - @State private var isShowPhotoPicker = false - @State private var noticeViewHeight: CGFloat = UIFont.systemFontSize - - let columns = [ - GridItem(.flexible()), - GridItem(.flexible()), - GridItem(.flexible()), - GridItem(.flexible()), - GridItem(.flexible()) - ] - - let chatColumns = [GridItem(.flexible())] - - @StateObject var keyboardHandler = KeyboardHandler() - @StateObject var viewModel = LiveRoomViewModel() - - var body: some View { - ZStack { - Color.black.edgesIgnoringSafeArea(.all) - - VStack(spacing: 0) { - LazyVStack(alignment: .leading, spacing: 0) { - HStack(spacing: 6.7) { - Text( - UserDefaults.int(forKey: .userId) == viewModel.liveRoomInfo?.creatorId ? - "라이브 종료": - "나가기" - ) - .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "ff5c49")) - .padding(.horizontal, 14.3) - .padding(.vertical, 8.3) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: "ff5c49"), lineWidth: 1) - ) - .onTapGesture { - viewModel.isExpandNotice = false - if let liveRoomInfo = viewModel.liveRoomInfo, liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - viewModel.isShowLiveEndPopup = true - } else { - viewModel.isShowQuitPopup = true - } - } - - Spacer() - - Text(viewModel.isBgOn ? "배경 ON" : "배경 OFF") - .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: viewModel.isBgOn ? "9970ff" : "eeeeee")) - .padding(.horizontal, 14.3) - .padding(.vertical, 8.3) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: viewModel.isBgOn ? "9970ff" : "bbbbbb"), lineWidth: 1) - ) - .onTapGesture { - viewModel.isBgOn.toggle() - } - - HStack(spacing: 4.7) { - Image("ic_share") - .resizable() - .frame(width: 16, height: 16) - } - .padding(.horizontal, 14.3) - .padding(.vertical, 6) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: "bbbbbb"), lineWidth: 1) - ) - .onTapGesture { - viewModel.shareRoom() - } - - if let liveRoomInfo = viewModel.liveRoomInfo, - liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - HStack(spacing: 4.7) { - Image("ic_edit") - .resizable() - .frame(width: 16, height: 16) - } - .padding(.horizontal, 14.3) - .padding(.vertical, 6) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: "bbbbbb"), lineWidth: 1) - ) - .onTapGesture { - viewModel.isShowEditRoomInfoDialog = true - } - } - } - .padding(.horizontal, 13.3) - - if let liveRoomInfo = viewModel.liveRoomInfo { - ZStack { - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 5.3) { - if liveRoomInfo.isAdult { - Text("19") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "e33621")) - .padding(.horizontal, 5.3) - .padding(.vertical, 3.3) - .background(Color(hex: "601d14")) - .cornerRadius(2.6) - } - - Text(liveRoomInfo.title) - .font(.custom(Font.bold.rawValue, size: 15.3)) - .foregroundColor(Color(hex: "eeeeee")) - .lineLimit(1) - } - .padding(.top, 16.7) - .padding(.horizontal, 13.3) - - LiveRoomTopCreatorView( - creatorId: liveRoomInfo.creatorId, - nickname: liveRoomInfo.creatorNickname, - profileImageUrl: liveRoomInfo.creatorProfileUrl, - isFollowing: liveRoomInfo.isFollowing, - onClickProfile: { - if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) { - viewModel.getUserProfile(userId: liveRoomInfo.creatorId) - } - }, - onClickFollow: { - if $0 { - viewModel.creatorUnFollow() - } else { - viewModel.creatorFollow() - } - } - ) - .padding(.top, 16.7) - .padding(.horizontal, 13.3) - - Rectangle() - .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.3)) - .padding(.horizontal, 13.3) - .padding(.top, 8) - - NotificationView(liveRoomInfo: liveRoomInfo) - } - - if viewModel.isMute { - Image("img_noti_mute") - } - } - - if viewModel.isShowNotice { - HStack(alignment: .top, spacing: 8) { - Text("[공지]") - .font(.custom(Font.bold.rawValue, size: 11.3)) - .foregroundColor(.white) - .onTapGesture { - viewModel.isExpandNotice.toggle() - } - - if viewModel.isExpandNotice { - VStack(spacing: 10) { - TextView(text: liveRoomInfo.notice, dynamicHeight: $noticeViewHeight) - .frame(height: viewModel.isExpandNotice ? noticeViewHeight : UIFont.systemFontSize) - - Text("닫기") - .font(.custom(Font.light.rawValue, size: 11.3)) - .foregroundColor(.white) - .onTapGesture { - viewModel.isExpandNotice = false - } - } - } else { - Text(liveRoomInfo.notice) - .font(.custom(Font.light.rawValue, size: 11.3)) - .foregroundColor(.white) - .lineLimit(1) - .onTapGesture { - viewModel.isExpandNotice = true - } - } - } - .padding(.horizontal, 26.7) - .padding(.vertical, 13.3) - .frame(width: screenSize().width, alignment: .leading) - .background(Color(hex: "3d2a6c")) - .padding(.top, 10) - .contentShape(Rectangle()) - } - - if !viewModel.isSpeakerFold { - HStack(spacing: 0) { - Text("스피커") - .font(.custom(Font.bold.rawValue, size: 12)) - .foregroundColor(Color(hex: "eeeeee")) - - Spacer() - } - .padding(.top, 20) - .padding(.horizontal, 23.3) - - LazyVGrid(columns: columns) { - ForEach(liveRoomInfo.speakerList, id: \.self) { speaker in - VStack(spacing: 6.7) { - ZStack { - KFImage(URL(string: speaker.profileImage)) - .resizable() - .scaledToFill() - .frame(width: 46.7, height: 46.7, alignment: .top) - .clipShape(Circle()) - .overlay( - Circle() - .stroke( - Color(hex: "9970ff"), - lineWidth: viewModel.activeSpeakers.contains(UInt(speaker.id)) ? 3 : 0 - ) - ) - - if viewModel.muteSpeakers.contains(UInt(speaker.id)) { - Image("ic_mute") - .resizable() - .frame(width: 46.7, height: 46.7) - } - - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 0) { - Spacer() - - if liveRoomInfo.creatorId == speaker.id { - Image("ic_crown") - .resizable() - .frame(width: 16.7, height: 16.7) - } - } - - Spacer() - } - } - .frame(width: 46.7, height: 46.7) - - Text(speaker.nickname) - .font(.custom(Font.light.rawValue, size: 12)) - .foregroundColor(Color(hex: "bbbbbb")) - .lineLimit(1) - } - .onTapGesture { - viewModel.selectedProfile = speaker - viewModel.isShowProfilePopup = true - } - } - } - .padding(.top, 16.7) - .padding(.horizontal, 23.3) - } - } - } - .padding(.vertical, 16.7) - .frame(width: screenSize().width) - .background(Color(hex: "222222")) - .cornerRadius(16.7, corners: [.topLeft, .topRight]) - - ZStack(alignment: .top) { - ScrollViewReader { proxy in - ZStack(alignment: .bottomTrailing) { - GeometryReader { proxy in - if let coverImageUrl = viewModel.coverImageUrl, viewModel.isBgOn { - KFImage(URL(string: coverImageUrl)) - .resizable() - .scaledToFill() - .frame(width: proxy.size.width, height: proxy.size.height, alignment: .center) - .clipped() - - Color.black.opacity(0.4) - } - } - - VStack(alignment: .leading, spacing: 0) { - ScrollView(.vertical, showsIndicators: false) { - scrollObservableView - ChatView() - .frame(width: screenSize().width) - } - .rotationEffect(Angle(degrees: 180)) - .onTapGesture { hideKeyboard() } - .onPreferenceChange(ScrollOffsetKey.self) { - viewModel.setOffset($0) - } - - InputChatView { - isShowingNewChat = false - proxy.scrollTo(viewModel.messages.count - 1, anchor: .center) - }.padding(.bottom, 10) - } - - VStack(spacing: 13.3) { - if let liveRoomInfo = viewModel.liveRoomInfo { - if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - Image("ic_roulette_settings") - .resizable() - .frame(width: 26.7, height: 26.7) - .padding(11) - .background(Color(hex: "525252").opacity(0.6)) - .cornerRadius(10) - .padding(.bottom, 13.3) - .onTapGesture { - viewModel.isShowRouletteSettings = true - } - } else if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) && viewModel.isActiveRoulette { - Image("ic_roulette") - .resizable() - .frame(width: 26.7, height: 26.7) - .padding(11) - .background(Color(hex: "525252").opacity(0.6)) - .cornerRadius(10) - .padding(.bottom, 13.3) - .onTapGesture { - viewModel.showRoulette() - } - } - } - - if viewModel.role == .SPEAKER { - Image(viewModel.isMute ? "ic_mic_off" : "ic_mic_on") - .resizable() - .frame(width: 26.7, height: 26.7) - .padding(11) - .background(Color(hex: "525252").opacity(0.6)) - .cornerRadius(10) - .padding(.bottom, 13.3) - .onTapGesture { - viewModel.toggleMute() - } - } - - Image(viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on") - .resizable() - .frame(width: 26.7, height: 26.7) - .padding(11) - .background(Color(hex: "525252").opacity(0.6)) - .cornerRadius(10) - .padding(.bottom, 13.3) - .onTapGesture { - viewModel.toggleSpeakerMute() - } - - if let liveRoomInfo = viewModel.liveRoomInfo, liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) && UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { - Image("ic_donation_message_list") - .resizable() - .frame(width: 26.7, height: 26.7) - .padding(11) - .background(Color(hex: "525252").opacity(0.6)) - .cornerRadius(10) - .onTapGesture { - viewModel.isShowDonationMessagePopup = true - } - } else { - Image("ic_donation") - .resizable() - .frame(width: 26.7, height: 26.7) - .padding(11) - .background(Color(hex: "525252").opacity(0.6)) - .cornerRadius(10) - .onTapGesture { - viewModel.isShowDonationPopup = true - } - } - } - .padding(.trailing, 16.7) - .padding(.bottom, 85) - - if isShowingNewChat { - NewChatView{ - isShowingNewChat = false - proxy.scrollTo(viewModel.messages.count - 1, anchor: .center) - }.padding(.bottom, 70) - } - } - .frame(width: screenSize().width) - } - - HStack(spacing: 0) { - Spacer() - - HStack(spacing: 6.7) { - Image(viewModel.isSpeakerFold ? "ic_live_detail_bottom" : "ic_live_detail_top") - .resizable() - .frame(width: 20, height: 20) - - Text(viewModel.isSpeakerFold ? "펼치기" : "접기") - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "bbbbbb")) - } - .padding(.vertical, 6.7) - .padding(.horizontal, 13.3) - .background(Color(hex: "222222")) - .cornerRadius(10, corners: [.bottomLeft, .bottomRight]) - .onTapGesture { - viewModel.isSpeakerFold.toggle() - } - } - .background( - LinearGradient( - gradient: Gradient(colors: [Color(hex: "222222").opacity(0.95), Color.black.opacity(0.005)]), - startPoint: .top, - endPoint: .bottom - ).ignoresSafeArea() - ) - } - } - .popup(isPresented: $viewModel.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1.3) { - GeometryReader { geo in - HStack { - Spacer() - Text(viewModel.errorMessage) - .padding(.vertical, 13.3) - .frame(width: geo.size.width - 66.7, alignment: .center) - .font(.custom(Font.medium.rawValue, size: 12)) - .background(Color(hex: "9970ff")) - .foregroundColor(Color.white) - .multilineTextAlignment(.center) - .cornerRadius(20) - .padding(.top, 66.7) - Spacer() - } - .onDisappear { - viewModel.quitRoom() - } - } - } - .cornerRadius(16.7, corners: [.topLeft, .topRight]) - .offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0)) - .onAppear { - UIApplication.shared.isIdleTimerDisabled = true - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - - viewModel.getMemberCan() - viewModel.initAgoraEngine() - viewModel.getRoomInfo() - - NotificationCenter.default.addObserver( - forName: UIApplication.willTerminateNotification, - object: nil, - queue: .main) { _ in - viewModel.quitRoom() - sleep(3) - } - } - .onDisappear { - UIApplication.shared.isIdleTimerDisabled = false - NotificationCenter.default.removeObserver(self) - } - - ZStack { - if viewModel.isShowProfilePopup, let liveRoomInfo = viewModel.liveRoomInfo, let selectedProfile = viewModel.selectedProfile { - LiveRoomProfileDialog( - isShowing: $viewModel.isShowProfilePopup, - profileInfo: selectedProfile, - creatorId: liveRoomInfo.creatorId, - isSpeaker: viewModel.role == .SPEAKER, - onClickInviteSpeaker: { inviteSpeaker(peerId: $0) }, - onClickChangeListener: { - if $0 == UserDefaults.int(forKey: .userId) { - viewModel.setListener() - return - } - - viewModel.changeListener(peerId: $0) - } - ) - } - - if viewModel.isShowDonationPopup { - LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: false) { can, message in - viewModel.donation(can: can, message: message) - } - } - - if viewModel.isShowQuitPopup { - SodaDialog( - title: "라이브 나가기", - desc: "라이브에서 나가시겠습니까?", - confirmButtonTitle: "예", - confirmButtonAction: { - viewModel.isShowQuitPopup = false - viewModel.quitRoom() - }, - cancelButtonTitle: "아니오", - cancelButtonAction: { - viewModel.isShowQuitPopup = false - } - ) - } - - if viewModel.isShowLiveEndPopup { - SodaDialog( - title: "라이브 종료", - desc: "라이브를 종료하시겠습니까?\n" + - "라이브를 종료하면 대화내용은\n" + - "저장되지 않고 사라집니다.\n" + - "참여자들 또한 라이브가 종료되어\n" + - "강제퇴장 됩니다.", - confirmButtonTitle: "예", - confirmButtonAction: { - viewModel.isShowLiveEndPopup = false - viewModel.quitRoom() - }, - cancelButtonTitle: "아니오", - cancelButtonAction: { - viewModel.isShowLiveEndPopup = false - } - ) - } - } - - ZStack { - if viewModel.isShowProfileList, let liveRoomInfo = viewModel.liveRoomInfo { - LiveRoomProfilesDialogView( - isShowing: $viewModel.isShowProfileList, - viewModel: viewModel, - roomInfo: liveRoomInfo, - registerNotification: { viewModel.creatorFollow() }, - unRegisterNotification: { viewModel.creatorUnFollow() }, - onClickProfile: { - if $0 != UserDefaults.int(forKey: .userId) { - viewModel.getUserProfile(userId: $0) - } - }, - onClickNoChatting: { userId, nickname, profileUrl in - viewModel.noChattingUserId = userId - viewModel.noChattingUserNickname = nickname - viewModel.noChattingUserProfileUrl = profileUrl - viewModel.isShowNoChattingConfirm = true - } - ) - } - - if viewModel.isShowUserProfilePopup, let userProfile = viewModel.userProfile { - Color.black.opacity(0.7) - .edgesIgnoringSafeArea(.all) - - LiveRoomUserProfileDialogView( - isShowing: $viewModel.isShowUserProfilePopup, - viewModel: viewModel, - userProfile: userProfile, - onClickSetManager: { - viewModel.setManagerMessageToPeer(userId: $0) - viewModel.setManager(userId: $0) - }, - onClickReleaseManager: { viewModel.changeListener(peerId: $0, isFromManager: true) }, - onClickFollow: { viewModel.creatorFollow(creatorId: $0, isGetUserProfile: true) }, - onClickUnFollow: { viewModel.creatorUnFollow(creatorId: $0, isGetUserProfile: true) }, - onClickInviteSpeaker: { inviteSpeaker(peerId: $0) }, - onClickChangeListener: { - viewModel.changeListener(peerId: $0) - }, - onClickMenu: { userId, userNickname, isBlocked in - viewModel.reportUserId = userId - viewModel.reportUserNickname = userNickname - viewModel.reportUserIsBlocked = isBlocked - viewModel.isShowReportMenu = true - }, - onClickNoChatting: { userId, nickname, profileUrl in - viewModel.noChattingUserId = userId - viewModel.noChattingUserNickname = nickname - viewModel.noChattingUserProfileUrl = profileUrl - viewModel.isShowNoChattingConfirm = true - } - ) - .padding(20) - .popup(isPresented: $viewModel.isShowReportPopup, type: .toast, position: .top, autohideIn: 1.3) { - GeometryReader { geo in - HStack { - Spacer() - Text(viewModel.reportMessage) - .padding(.vertical, 13.3) - .frame(width: geo.size.width - 66.7, alignment: .center) - .font(.custom(Font.medium.rawValue, size: 12)) - .background(Color(hex: "9970ff")) - .foregroundColor(Color.white) - .multilineTextAlignment(.center) - .cornerRadius(20) - .padding(.top, 66.7) - Spacer() - } - } - } - } - - if viewModel.isShowReportMenu { - VStack(spacing: 0) { - ProfileReportMenuView( - isShowing: $viewModel.isShowReportMenu, - isBlockedUser: viewModel.reportUserIsBlocked, - userBlockAction: { viewModel.isShowUesrBlockConfirm = true }, - userUnBlockAction: { viewModel.userUnBlock() }, - userReportAction: { viewModel.isShowUesrReportView = true }, - profileReportAction: { viewModel.isShowProfileReportConfirm = true } - ) - - Rectangle() - .foregroundColor(Color(hex: "222222")) - .frame(width: screenSize().width, height: 15.3) - } - .ignoresSafeArea() - } - - if viewModel.isShowUesrBlockConfirm { - UserBlockConfirmDialogView( - isShowing: $viewModel.isShowUesrBlockConfirm, - nickname: viewModel.reportUserNickname, - confirmAction: { - viewModel.userBlock { userId in - viewModel.kickOutId = userId - viewModel.kickOut() - } - } - ) - } - - if viewModel.isShowUesrReportView { - UserReportDialogView( - isShowing: $viewModel.isShowUesrReportView, - confirmAction: { reason in - viewModel.report(type: .USER, reason: reason) - } - ) - } - - if viewModel.isShowProfileReportConfirm { - ProfileReportDialogView( - isShowing: $viewModel.isShowProfileReportConfirm, - confirmAction: { - viewModel.report(type: .PROFILE) - } - ) - } - - if viewModel.isShowNoChattingConfirm && viewModel.noChattingUserId > 0 { - LiveRoomNoChattingDialogView( - nickname: viewModel.noChattingUserNickname, - profileUrl: viewModel.noChattingUserProfileUrl, - confirmAction: { - viewModel.isShowNoChattingConfirm = false - viewModel.setNoChatting() - }, - cancelAction: { - viewModel.noChattingUserId = 0 - viewModel.noChattingUserNickname = "" - viewModel.noChattingUserProfileUrl = "" - viewModel.isShowNoChattingConfirm = false - } - ) - } - - if viewModel.isShowPopup { - LiveRoomDialogView( - content: viewModel.popupContent, - cancelTitle: viewModel.popupCancelTitle, - cancelAction: viewModel.popupCancelAction, - confirmTitle: viewModel.popupConfirmTitle, - confirmAction: viewModel.popupConfirmAction - ).onAppear { - if viewModel.popupConfirmTitle == nil && viewModel.popupConfirmAction == nil { - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - viewModel.isShowPopup = false - viewModel.popupCancelTitle = nil - viewModel.popupCancelAction = nil - viewModel.popupConfirmTitle = nil - viewModel.popupConfirmAction = nil - } - } - } - } - } - - if viewModel.isShowRouletteSettings { - RouletteSettingsView(isShowing: $viewModel.isShowRouletteSettings) { isActiveRoulette in - self.viewModel.setActiveRoulette(isActiveRoulette: isActiveRoulette) - } - } - - if let preview = viewModel.roulettePreview, viewModel.isShowRoulettePreview { - RoulettePreviewDialog( - isShowing: $viewModel.isShowRoulettePreview, - title: nil, - onClickSpin: { spinRoulette() }, - preview: preview - ) - } - - if viewModel.isShowRoulette { - RouletteViewDialog(isShowing: $viewModel.isShowRoulette, options: viewModel.rouletteItems, selectedOption: viewModel.rouletteSelectedItem) { - viewModel.sendRouletteDonation() - } - } - - if viewModel.isLoading && viewModel.liveRoomInfo == nil { - LoadingView() - } - } - .ignoresSafeArea(.keyboard) - .edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init()) - .sheet( - isPresented: $viewModel.isShowShareView, - onDismiss: { viewModel.shareMessage = "" }, - content: { - ActivityViewController(activityItems: [viewModel.shareMessage]) - } - ) - .sheet(isPresented: $isShowPhotoPicker) { - ImagePicker( - isShowing: $isShowPhotoPicker, - selectedImage: $viewModel.coverImage, - sourceType: .photoLibrary - ) - } - .sheet(isPresented: $viewModel.isShowEditRoomInfoDialog) { - if let liveRoomInfo = viewModel.liveRoomInfo { - LiveRoomInfoEditDialog( - isShowing: $viewModel.isShowEditRoomInfoDialog, - isShowPhotoPicker: $isShowPhotoPicker, - viewModel: viewModel, - isLoading: viewModel.isLoading, - currentTitle: liveRoomInfo.title, - currentNotice: liveRoomInfo.notice, - coverImageUrl: liveRoomInfo.coverImageUrl, - coverImage: viewModel.coverImage - ) { newTitle, newNotice in - self.viewModel.editLiveRoomInfo( - title: newTitle, - notice: newNotice - ) - } - } else { - EmptyView() - .onAppear { - viewModel.isShowEditRoomInfoDialog = false - } - } - } - .sheet(isPresented: $viewModel.isShowDonationRankingPopup) { - LiveRoomDonationRankingDialog(isShowing: $viewModel.isShowDonationRankingPopup) - } - .sheet(isPresented: $viewModel.isShowDonationMessagePopup) { - LiveRoomDonationMessageDialog(isShowing: $viewModel.isShowDonationMessagePopup) - } - } - - func makeAttributedString(_ text: String) -> NSAttributedString { - let attributedString = NSMutableAttributedString(string: text) - - let urlRegex = try! NSRegularExpression(pattern: "\\b(https?://\\S+\\b|www\\.\\S+\\b)") - let matches = urlRegex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text)) - - for match in matches { - let url = (text as NSString).substring(with: match.range) - if let detectedURL = URL(string: url) { - attributedString.addAttribute(.link, value: detectedURL, range: match.range) - } - } - - return attributedString - } - - - private func inviteSpeaker(peerId: Int) { - if viewModel.liveRoomInfo!.speakerList.count <= 4 { - viewModel.inviteSpeaker(peerId: peerId) - self.viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요." - self.viewModel.isShowPopup = true - } else { - viewModel.popupContent = "스피커 정원을 초과했습니다." - viewModel.isShowPopup = true - } - } - - private var scrollObservableView: some View { - GeometryReader { proxy in - let offsetY = proxy.frame(in: .global).origin.y - Color.clear - .preference( - key: ScrollOffsetKey.self, - value: offsetY - ) - .onAppear { - viewModel.setOriginOffset(offsetY) - } - } - .frame(height: 0) - } - - struct ScrollOffsetKey: PreferenceKey { - static var defaultValue: CGFloat = .zero - static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { - value += nextValue() - } - } - - @ViewBuilder - func NotificationView(liveRoomInfo: GetRoomInfoResponse) -> some View { - HStack(spacing: 8) { - Image( - viewModel.isShowNotice ? - "ic_notice_selected" : - "ic_notice_normal" - ) - .contentShape(Rectangle()) - .onTapGesture { - viewModel.isShowNotice.toggle() - } - .padding(.trailing, 10) - - Spacer() - - HStack(spacing: 4.7) { - Image("ic_can") - .resizable() - .frame(width: 16, height: 16) - - Text("\(viewModel.totalDonationCan)") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "bbbbbb")) - } - .padding(.horizontal, 11.5) - .padding(.vertical, 5.3) - .overlay( - RoundedRectangle(cornerRadius: 12.8) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "eeeeee")) - ) - .contentShape(Rectangle()) - .onTapGesture { - viewModel.isShowDonationRankingPopup = true - } - - if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - HStack(spacing: 0) { - Text("참여자") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "bbbbbb")) - - Text("\(liveRoomInfo.participantsCount)") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "9970ff")) - .padding(.leading, 6.7) - } - .padding(.horizontal, 11.5) - .padding(.vertical, 7.3) - .overlay( - RoundedRectangle(cornerRadius: 12.8) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "eeeeee")) - ) - .contentShape(Rectangle()) - .onTapGesture { - viewModel.isShowProfileList = true - } - } - } - .padding(.top, 13.3) - .padding(.horizontal, 13.3) - } - - @ViewBuilder - func NewChatView(scrollToBottom: @escaping () -> Void) -> some View { - HStack(spacing: 0) { - Spacer() - - HStack(spacing: 6.7) { - Image("ic_bottom_white") - Text("새로운 채팅") - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - } - .padding(.vertical, 8) - .padding(.horizontal, 13.3) - .background(Color(hex: "555555").opacity(0.8)) - .cornerRadius(16.7) - .padding(.bottom, 13.3) - .onTapGesture { scrollToBottom() } - - Spacer() - } - } - - @ViewBuilder - func InputChatView(scrollToBottom: @escaping () -> Void) -> some View { - HStack(spacing: 0) { - TextField("채팅을 입력하세요", text: $viewModel.chatMessage) - .autocapitalization(.none) - .disableAutocorrection(true) - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .accentColor(Color(hex: "3bb9f1")) - .keyboardType(.default) - .padding(.horizontal, 13.3) - .onTapGesture { - if viewModel.isNoChatting { - viewModel.popupContent = "\(viewModel.remainingNoChattingTime)초 동안 채팅하실 수 없습니다" - viewModel.isShowPopup = true - } - } - - Spacer() - - Image("btn_message_send") - .resizable() - .frame(width: 35, height: 35) - .padding(6.7) - .onTapGesture { - viewModel.sendMessage() - scrollToBottom() - } - } - .background(Color(hex: "232323")) - .cornerRadius(10) - .overlay( - RoundedRectangle(cornerRadius: 10) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "eeeeee")) - ) - .padding(13.3) - } - - @ViewBuilder - func ChatView() -> some View { - LazyVGrid(columns: chatColumns, alignment: .leading, spacing: 20) { - ForEach(0.. (56.7 * 2) { - isShowingNewChat = true - } - } - } - - private func spinRoulette() { - viewModel.spinRoulette() - } -} - -struct LiveRoomView_Previews: PreviewProvider { - static var previews: some View { - LiveRoomView() - } -} - -struct TextView: UIViewRepresentable { - var text: String - @Binding var dynamicHeight: CGFloat - - func makeUIView(context: Context) -> UITextView { - let textView = UITextView() - textView.text = text - textView.isEditable = false - textView.isScrollEnabled = true - textView.backgroundColor = .clear - textView.dataDetectorTypes = .link - textView.font = UIFont(name: Font.light.rawValue, size: 11.3) - textView.textColor = .white - textView.textContainer.lineFragmentPadding = 0 - textView.textContainerInset = .zero - - return textView - } - - func updateUIView(_ uiView: UITextView, context: Context) { - uiView.text = text - DispatchQueue.main.async { - let height = uiView.sizeThatFits(uiView.frame.size).height - self.dynamicHeight = height > 500 ? 500 : height - } - } - - func makeCoordinator() -> Coordinator { - Coordinator($dynamicHeight) - } - - class Coordinator: NSObject, UITextViewDelegate { - var dynamicHeight: Binding - - init(_ dynamicHeight: Binding) { - self.dynamicHeight = dynamicHeight - } - - func textViewDidChange(_ textView: UITextView) { - DispatchQueue.main.async { - self.dynamicHeight.wrappedValue = textView.sizeThatFits(textView.frame.size).height - } - } - } -} - diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index b8fca5e..616ea28 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -24,7 +24,6 @@ final class LiveRoomViewModel: NSObject, ObservableObject { private let rouletteRepository = RouletteRepository() private var subscription = Set() - @Published var chatMessage = "" @Published var isSpeakerMute = false @Published var isMute = false @Published var role = LiveRoomMemberRole.LISTENER @@ -68,15 +67,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } @Published var selectedProfile: LiveRoomMember? - @Published var isShowNotice = true { - didSet { - if !isShowNotice { - isExpandNotice = false - } - } - } - - @Published var isExpandNotice = false + @Published var isShowNotice = false @Published var isShowDonationPopup = false @@ -108,6 +99,11 @@ final class LiveRoomViewModel: NSObject, ObservableObject { @Published var donationMessageList = [LiveRoomDonationMessage]() @Published var donationMessageCount = 0 + @Published var isShowingNewChat = false + @Published var isShowPhotoPicker = false + @Published var noticeViewWidth: CGFloat = UIFont.systemFontSize + @Published var noticeViewHeight: CGFloat = UIFont.systemFontSize + @Published var isBgOn = true @Published var donationStatus: GetLiveRoomDonationStatusResponse? @@ -316,7 +312,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { agora.speakerMute(isSpeakerMute) } - func sendMessage() { + func sendMessage(chatMessage: String, onSuccess: @escaping () -> Void) { DispatchQueue.main.async {[unowned self] in if isNoChatting { self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다" @@ -334,7 +330,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } } - self.chatMessage = "" + onSuccess() }) } } diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift new file mode 100644 index 0000000..2d69078 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift @@ -0,0 +1,40 @@ +// +// LiveRoomNewChatView.swift +// SodaLive +// +// Created by klaus on 2024/01/18. +// + +import SwiftUI + +struct LiveRoomNewChatView: View { + + let scrollToBottom: () -> Void + + var body: some View { + HStack(spacing: 0) { + Spacer() + + HStack(spacing: 6.7) { + Image("ic_bottom_white") + Text("새로운 채팅") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.grayee) + } + .padding(.vertical, 8) + .padding(.horizontal, 13.3) + .background(Color.gray55.opacity(0.8)) + .cornerRadius(16.7) + .padding(.bottom, 13.3) + .onTapGesture { scrollToBottom() } + + Spacer() + } + } +} + +struct LiveRoomNewChatView_Previews: PreviewProvider { + static var previews: some View { + LiveRoomNewChatView {} + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift new file mode 100644 index 0000000..e80b2ea --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift @@ -0,0 +1,43 @@ +// +// LiveRoomOverlayStrokeImageButton.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomOverlayStrokeImageButton: View { + + let imageName: String + let strokeColor: Color + let strokeWidth: CGFloat + let strokeCornerRadius: CGFloat + + let onClick: () -> Void + + var body: some View { + Image(imageName) + .padding(4) + .overlay( + RoundedRectangle(cornerRadius: strokeCornerRadius) + .stroke( + strokeColor, + lineWidth: strokeWidth + ) + ) + .onTapGesture { onClick() } + } +} + +struct LiveRoomOverlayStrokeImageButton_Previews: PreviewProvider { + static var previews: some View { + LiveRoomOverlayStrokeImageButton( + imageName: "ic_edit", + strokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3, + onClick: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift new file mode 100644 index 0000000..8e8d866 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift @@ -0,0 +1,45 @@ +// +// LiveRoomOverlayStrokeTextButton.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomOverlayStrokeTextButton: View { + + let text: String + let textColor: Color + let strokeColor: Color + let strokeWidth: CGFloat + let strokeCornerRadius: CGFloat + + let onClick: () -> Void + + var body: some View { + Text(text) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.red) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .overlay( + RoundedRectangle(cornerRadius: strokeCornerRadius) + .stroke(strokeColor, lineWidth: strokeWidth) + ) + .onTapGesture { onClick() } + } +} + +struct LiveRoomOverlayStrokeTextButton_Previews: PreviewProvider { + static var previews: some View { + LiveRoomOverlayStrokeTextButton( + text: "라이브 종료", + textColor: Color.mainRed, + strokeColor: Color.mainRed, + strokeWidth: 1, + strokeCornerRadius: 5.3, + onClick: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift new file mode 100644 index 0000000..5e6f88b --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift @@ -0,0 +1,59 @@ +// +// LiveRoomOverlayStrokeTextToggleButton.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomOverlayStrokeTextToggleButton: View { + + let isOn: Bool + + let onText: String + let onTextColor: Color + let onStrokeColor: Color + + let offText: String? + let offTextColor: Color + let offStrokeColor: Color + + let strokeWidth: CGFloat + let strokeCornerRadius: CGFloat + + let onClick: () -> Void + + var body: some View { + Text(isOn ? onText : offText != nil && offText?.count ?? 0 > 0 ? offText! : onText) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(isOn ? onTextColor : offTextColor) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .overlay( + RoundedRectangle(cornerRadius: strokeCornerRadius) + .stroke( + isOn ? onStrokeColor : offStrokeColor, + lineWidth: strokeWidth + ) + ) + .onTapGesture { onClick() } + } +} + +struct LiveRoomOverlayStrokeTextToggleButton_Previews: PreviewProvider { + static var previews: some View { + LiveRoomOverlayStrokeTextToggleButton( + isOn: true, + onText: "배경 ON", + onTextColor: Color.button, + onStrokeColor: Color.button, + offText: "배경 OFF", + offTextColor: Color.grayee, + offStrokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3, + onClick: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift new file mode 100644 index 0000000..5d46c7b --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift @@ -0,0 +1,33 @@ +// +// LiveRoomRightBottomButton.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomRightBottomButton: View { + + let imageName: String + let onClick: () -> Void + + var body: some View { + Image(imageName) + .resizable() + .frame(width: 24, height: 24) + .padding(10) + .background(Color.gray52.opacity(0.6)) + .cornerRadius(10) + .onTapGesture { onClick() } + } +} + +struct LiveRoomRightBottomButton_Previews: PreviewProvider { + static var previews: some View { + LiveRoomRightBottomButton( + imageName: "ic_donation", + onClick: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift b/SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift new file mode 100644 index 0000000..02b89de --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift @@ -0,0 +1,31 @@ +// +// TextView.swift +// SodaLive +// +// Created by klaus on 2024/01/18. +// + +import SwiftUI +import UIKit + +struct DetectableTextView: UIViewRepresentable { + var text: String + + func makeUIView(context: Context) -> UITextView { + let textView = UITextView() + textView.isEditable = false // Make it readonly + textView.backgroundColor = .clear + textView.isScrollEnabled = true + textView.dataDetectorTypes = .link + textView.font = UIFont(name: Font.light.rawValue, size: 11.3) + textView.textColor = .white + textView.textContainer.lineFragmentPadding = 0 + textView.textContainerInset = .zero + + return textView + } + + func updateUIView(_ uiView: UITextView, context: Context) { + uiView.text = text + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift new file mode 100644 index 0000000..9b614b6 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift @@ -0,0 +1,67 @@ +// +// LiveRoomChatView.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomChatView: View { + + let messages: [LiveRoomChat] + let getUserProfile: (Int) -> Void + + var body: some View { + LazyVStack(alignment: .leading, spacing: 18) { + ForEach(0.. Void + let onClickProfile: () -> Void + + var body: some View { + HStack(spacing: 5.3) { + ZStack(alignment: .center) { + KFImage(URL(string: creatorProfileUrl)) + .resizable() + .frame(width: 33.3, height: 33.3) + .clipShape(Circle()) + .overlay( + Circle() + .stroke( + Color.button, + lineWidth: isActiveSpeaker ? 3 : 0 + ) + ) + .onTapGesture { onClickProfile() } + + if isMute { + Image("ic_mute") + .resizable() + .frame(width: 33.3, height: 33.3) + } + } + + VStack(alignment: .leading, spacing: 2.7) { + HStack(spacing: 2.7) { + if isAdult { + Text("19") + .font(.custom(Font.bold.rawValue, size: 8)) + .foregroundColor(.white) + .padding(.vertical, 2.8) + .padding(.horizontal, 2) + .background(Circle().foregroundColor(Color.mainRed2)) + } + + Text(roomTitle) + .font(.custom(Font.bold.rawValue, size: 12)) + .foregroundColor(.grayee) + .lineLimit(1) + } + + Text(creatorNickname) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(.gray77) + } + + if isShowFollowingButton { + Image(isFollowing ? "btn_select_checked" : "btn_plus_round") + .resizable() + .frame(width: 20, height: 20) + .onTapGesture { onClickFollow() } + } + + } + .padding(.vertical, 8) + .padding(.horizontal, 5.3) + .overlay( + RoundedRectangle(cornerRadius: 5.3) + .stroke(Color.graybb, lineWidth: 1) + ) + } +} + +struct LiveRoomInfoCreatorView_Previews: PreviewProvider { + static var previews: some View { + LiveRoomInfoCreatorView( + roomTitle: "오늘 라이브방송은 OOO 입니다.", + creatorNickname: "도화", + creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320", + isMute: false, + isAdult: false, + isFollowing: true, + isActiveSpeaker: true, + isShowFollowingButton: true, + onClickFollow: {}, + onClickProfile: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift new file mode 100644 index 0000000..9ff4c76 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift @@ -0,0 +1,190 @@ +// +// LiveRoomInfoGuestView.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomInfoGuestView: View { + + let title: String + let totalDonationCan: Int + + let isOnBg: Bool + let isOnNotice: Bool + + let creatorId: Int + let creatorNickname: String + let creatorProfileUrl: String + let speakerList: [LiveRoomMember] + let muteSpeakerList: [UInt] + let activeSpeakerList: [UInt] + + let isFollowing: Bool + let isAdult: Bool + + let onClickQuit: () -> Void + let onClickToggleBg: () -> Void + let onClickShare: () -> Void + let onClickFollow: (Bool) -> Void + let onClickProfile: (Int) -> Void + let onClickNotice: () -> Void + let onClickTotalDonation: () -> Void + + var body: some View { + ZStack { + VStack(spacing: 13.3) { + HStack(spacing: 5.3) { + LiveRoomOverlayStrokeTextButton( + text: "나가기", + textColor: Color.red, + strokeColor: Color.red, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickQuit() } + + Spacer() + + LiveRoomOverlayStrokeTextToggleButton( + isOn: isOnBg, + onText: "배경 ON", + onTextColor: Color.button, + onStrokeColor: Color.button, + offText: "배경 OFF", + offTextColor: Color.graybb, + offStrokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickToggleBg() } + + LiveRoomOverlayStrokeImageButton( + imageName: "ic_share", + strokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickShare() } + } + + HStack(spacing: 8) { + LiveRoomInfoCreatorView( + roomTitle: title, + creatorNickname: creatorNickname, + creatorProfileUrl: creatorProfileUrl, + isMute: muteSpeakerList.contains(UInt(creatorId)), + isAdult: isAdult, + isFollowing: isFollowing, + isActiveSpeaker: activeSpeakerList.contains(UInt(creatorId)), + isShowFollowingButton: true, + onClickFollow: { onClickFollow(isFollowing) }, + onClickProfile: { onClickProfile(creatorId) } + ) + .frame(width: 180, alignment: .leading) + + Spacer() + + ForEach(0.. Void + let onClickToggleBg: () -> Void + let onClickShare: () -> Void + let onClickEdit: () -> Void + let onClickProfile: (Int) -> Void + let onClickNotice: () -> Void + let onClickTotalDonation: () -> Void + let onClickParticipants: () -> Void + + var body: some View { + ZStack { + VStack(alignment: .leading, spacing: 13.3) { + HStack(spacing: 5.3) { + LiveRoomOverlayStrokeTextButton( + text: "라이브 종료", + textColor: Color.red, + strokeColor: Color.red, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickQuit() } + + Spacer() + + LiveRoomOverlayStrokeTextToggleButton( + isOn: isOnBg, + onText: "배경 ON", + onTextColor: Color.button, + onStrokeColor: Color.button, + offText: "배경 OFF", + offTextColor: Color.graybb, + offStrokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickToggleBg() } + + LiveRoomOverlayStrokeImageButton( + imageName: "ic_share", + strokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickShare() } + + LiveRoomOverlayStrokeImageButton( + imageName: "ic_edit", + strokeColor: Color.graybb, + strokeWidth: 1, + strokeCornerRadius: 5.3 + ) { onClickEdit() } + } + + HStack(spacing: 8) { + LiveRoomInfoCreatorView( + roomTitle: title, + creatorNickname: creatorNickname, + creatorProfileUrl: creatorProfileUrl, + isMute: muteSpeakerList.contains(UInt(creatorId)), + isAdult: isAdult, + isFollowing: false, + isActiveSpeaker: activeSpeakerList.contains(UInt(creatorId)), + isShowFollowingButton: false, + onClickFollow: {}, + onClickProfile: {} + ) + .frame(width: 180, alignment: .leading) + + Spacer() + + ForEach(0.. Void + + var body: some View { + VStack(spacing: 2.7) { + ZStack(alignment: .center) { + KFImage(URL(string: profileUrl)) + .resizable() + .frame(width: 26.7, height: 26.7) + .clipShape(Circle()) + .overlay( + Circle() + .stroke( + Color.button, + lineWidth: isActiveSpeaker ? 3 : 0 + ) + ) + + if isMute { + Image("ic_mute") + .resizable() + .frame(width: 30, height: 30) + } + } + + Text(nickname) + .font(.custom(Font.medium.rawValue, fixedSize: 10.7)) + .foregroundColor(.gray77) + } + .frame(width: 30, height: 30) + .onTapGesture { onClickProfile() } + } +} + +struct LiveRoomInfoSpeakerView_Previews: PreviewProvider { + static var previews: some View { + LiveRoomInfoSpeakerView( + nickname: "청령", + profileUrl: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417", + isMute: false, + isActiveSpeaker: true, + onClickProfile: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift new file mode 100644 index 0000000..0912a55 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift @@ -0,0 +1,55 @@ +// +// LiveRoomInputChatView.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI + +struct LiveRoomInputChatView: View { + + @State private var chatMessage = "" + + + let sendMessage: (String) -> Bool + + var body: some View { + HStack(spacing: 6.7) { + TextField("채팅을 입력하세요", text: $chatMessage) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(.graybb) + .accentColor(.button) + .keyboardType(.default) + .padding(.horizontal, 13.3) + .padding(.vertical, 18.3) + .background(Color.gray22) + .cornerRadius(5.3) + .frame(maxWidth: .infinity) + .overlay( + RoundedRectangle(cornerRadius: 5.3) + .strokeBorder(lineWidth: 1) + .foregroundColor(.gray77) + ) + + Image("btn_message_send") + .resizable() + .frame(width: 35, height: 35) + .padding(6.7) + .onTapGesture { + if sendMessage(chatMessage) { + chatMessage = "" + } + } + } + .padding(13.3) + } +} + +struct LiveRoomInputChatView_Previews: PreviewProvider { + static var previews: some View { + LiveRoomInputChatView(sendMessage: { _ in return true }) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift new file mode 100644 index 0000000..c796d31 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift @@ -0,0 +1,668 @@ +// +// LiveRoomViewV2.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import SwiftUI +import Kingfisher + +struct LiveRoomViewV2: View { + + @StateObject var keyboardHandler = KeyboardHandler() + @StateObject var viewModel = LiveRoomViewModel() + + @State private var textHeight: CGFloat = .zero + + var body: some View { + ZStack { + Color.black.edgesIgnoringSafeArea(.all) + + VStack(spacing: 0) { + if let liveRoomInfo = viewModel.liveRoomInfo { + if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { + LiveRoomInfoHostView( + title: liveRoomInfo.title, + totalDonationCan: viewModel.totalDonationCan, + participantsCount: liveRoomInfo.participantsCount, + isOnBg: viewModel.isBgOn, + isOnNotice: viewModel.isShowNotice, + creatorId: liveRoomInfo.creatorId, + creatorNickname: liveRoomInfo.creatorNickname, + creatorProfileUrl: liveRoomInfo.creatorProfileUrl, + speakerList: liveRoomInfo.speakerList, + muteSpeakerList: viewModel.muteSpeakers, + activeSpeakerList: viewModel.activeSpeakers, + isAdult: liveRoomInfo.isAdult, + onClickQuit: { + viewModel.isShowLiveEndPopup = true + }, + onClickToggleBg: { + viewModel.isBgOn.toggle() + }, + onClickShare: { + viewModel.shareRoom() + }, + onClickEdit: { + viewModel.isShowEditRoomInfoDialog = true + }, + onClickProfile: { + if $0 != UserDefaults.int(forKey: .userId) { + viewModel.getUserProfile(userId: $0) + } + }, + onClickNotice: { + viewModel.isShowNotice.toggle() + }, + onClickTotalDonation: { + viewModel.isShowDonationRankingPopup = true + }, + onClickParticipants: { + viewModel.isShowProfileList = true + } + ) + } else { + LiveRoomInfoGuestView( + title: liveRoomInfo.title, + totalDonationCan: viewModel.totalDonationCan, + isOnBg: viewModel.isBgOn, + isOnNotice: viewModel.isShowNotice, + creatorId: liveRoomInfo.creatorId, + creatorNickname: liveRoomInfo.creatorNickname, + creatorProfileUrl: liveRoomInfo.creatorProfileUrl, + speakerList: liveRoomInfo.speakerList, + muteSpeakerList: viewModel.muteSpeakers, + activeSpeakerList: viewModel.activeSpeakers, + isFollowing: liveRoomInfo.isFollowing, + isAdult: liveRoomInfo.isAdult, + onClickQuit: { + viewModel.isShowQuitPopup = true + }, + onClickToggleBg: { + viewModel.isBgOn.toggle() + }, + onClickShare: { + viewModel.shareRoom() + }, + onClickFollow: { + if $0 { + viewModel.creatorUnFollow() + } else { + viewModel.creatorFollow() + } + }, + onClickProfile: { + if $0 != UserDefaults.int(forKey: .userId) { + viewModel.getUserProfile(userId: $0) + } + }, + onClickNotice: { + viewModel.isShowNotice.toggle() + }, + onClickTotalDonation: { + viewModel.isShowDonationRankingPopup = true + } + ) + } + + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(.gray22) + .frame(height: 16) + .frame(maxWidth: .infinity) + + ScrollViewReader { proxy in + ZStack(alignment: .bottom) { + ZStack { + if viewModel.isBgOn { + KFImage(URL(string: liveRoomInfo.coverImageUrl)) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + Rectangle() + .foregroundColor(.black.opacity(0.25)) + .frame(maxWidth: .infinity) + + ScrollView(.vertical, showsIndicators: false) { + scrollObservableView + + LiveRoomChatView(messages: viewModel.messages) { + if $0 != UserDefaults.int(forKey: .userId) { + viewModel.getUserProfile(userId: $0) + } + } + .frame(width: screenSize().width) + .rotationEffect(Angle(degrees: 180)) + .valueChanged(value: viewModel.messageChangeFlag) { _ in + if viewModel.offset - viewModel.originOffset > (56.7 * 2) { + viewModel.isShowingNewChat = true + } + } + } + .rotationEffect(Angle(degrees: 180)) + .onTapGesture { hideKeyboard() } + .onPreferenceChange(ScrollOffsetKey.self) { + viewModel.setOffset($0) + } + .padding(.bottom, 70) + } + .padding(.top, 16) + + VStack(alignment: .trailing, spacing: 0) { + Spacer() + + VStack(spacing: 13.3) { + if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { + Image("ic_roulette_settings") + .resizable() + .frame(width: 26.7, height: 26.7) + .padding(11) + .background(Color(hex: "525252").opacity(0.6)) + .cornerRadius(10) + .onTapGesture { + viewModel.isShowRouletteSettings = true + } + } else if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) && viewModel.isActiveRoulette { + Image("ic_roulette") + .resizable() + .frame(width: 26.7, height: 26.7) + .padding(11) + .background(Color(hex: "525252").opacity(0.6)) + .cornerRadius(10) + .onTapGesture { + viewModel.showRoulette() + } + } + + if viewModel.role == .SPEAKER { + Image(viewModel.isMute ? "ic_mic_off" : "ic_mic_on") + .resizable() + .frame(width: 26.7, height: 26.7) + .padding(11) + .background(Color(hex: "525252").opacity(0.6)) + .cornerRadius(10) + .onTapGesture { + viewModel.toggleMute() + } + } + + Image(viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on") + .resizable() + .frame(width: 26.7, height: 26.7) + .padding(11) + .background(Color(hex: "525252").opacity(0.6)) + .cornerRadius(10) + .onTapGesture { + viewModel.toggleSpeakerMute() + } + + if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) && + UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { + Image("ic_donation_message_list") + .resizable() + .frame(width: 26.7, height: 26.7) + .padding(11) + .background(Color(hex: "525252").opacity(0.6)) + .cornerRadius(10) + .onTapGesture { + viewModel.isShowDonationMessagePopup = true + } + } else { + Image("ic_donation") + .resizable() + .frame(width: 26.7, height: 26.7) + .padding(11) + .background(Color(hex: "525252").opacity(0.6)) + .cornerRadius(10) + .onTapGesture { + viewModel.isShowDonationPopup = true + } + } + } + .padding(.trailing, 13.3) + + LiveRoomInputChatView { + viewModel.sendMessage(chatMessage: $0) { + viewModel.isShowingNewChat = false + proxy.scrollTo(viewModel.messages.count - 1, anchor: .center) + } + + return true + } + .padding(.bottom, 10) + } + + if viewModel.isShowingNewChat { + LiveRoomNewChatView{ + viewModel.isShowingNewChat = false + proxy.scrollTo(viewModel.messages.count - 1, anchor: .center) + }.padding(.bottom, 70) + } + } + } + + if viewModel.isShowNotice { + VStack(alignment: .leading, spacing: 0) { + Image("ic_notice_triangle") + .padding(.leading, 13.3) + + VStack(alignment: .leading, spacing: 8) { + Text("[방송공지]") + .font(.custom(Font.bold.rawValue, size: 11.3)) + .foregroundColor(.white) + + DetectableTextView(text: liveRoomInfo.notice) + .frame( + width: 280, + height: textHeight > 450 ? 450 : textHeight + ) + .onAppear { + self.textHeight = self.estimatedHeight( + for: liveRoomInfo.notice, + width: 280 + ) + } + .onChange(of: liveRoomInfo.notice) { newText in + self.textHeight = self.estimatedHeight( + for: newText, + width: 280 + ) + } + } + .padding(8) + .background(Color.gray33) + .padding(.horizontal, 13.3) + .padding(.bottom, 120) + } + } + } + } + } + .popup(isPresented: $viewModel.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1.3) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .frame(width: geo.size.width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color.button) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + .onDisappear { + viewModel.quitRoom() + } + } + } + .cornerRadius(16.7, corners: [.topLeft, .topRight]) + .offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0)) + .onAppear { + UIApplication.shared.isIdleTimerDisabled = true + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + + viewModel.getMemberCan() + viewModel.initAgoraEngine() + viewModel.getRoomInfo() + + NotificationCenter.default.addObserver( + forName: UIApplication.willTerminateNotification, + object: nil, + queue: .main) { _ in + viewModel.quitRoom() + sleep(3) + } + } + .onDisappear { + UIApplication.shared.isIdleTimerDisabled = false + NotificationCenter.default.removeObserver(self) + } + + ZStack { + if viewModel.isShowProfilePopup, let liveRoomInfo = viewModel.liveRoomInfo, let selectedProfile = viewModel.selectedProfile { + LiveRoomProfileDialog( + isShowing: $viewModel.isShowProfilePopup, + profileInfo: selectedProfile, + creatorId: liveRoomInfo.creatorId, + isSpeaker: viewModel.role == .SPEAKER, + onClickInviteSpeaker: { inviteSpeaker(peerId: $0) }, + onClickChangeListener: { + if $0 == UserDefaults.int(forKey: .userId) { + viewModel.setListener() + return + } + + viewModel.changeListener(peerId: $0) + } + ) + } + + if viewModel.isShowDonationPopup { + LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: false) { can, message in + viewModel.donation(can: can, message: message) + } + } + + if viewModel.isShowQuitPopup { + SodaDialog( + title: "라이브 나가기", + desc: "라이브에서 나가시겠습니까?", + confirmButtonTitle: "예", + confirmButtonAction: { + viewModel.isShowQuitPopup = false + viewModel.quitRoom() + }, + cancelButtonTitle: "아니오", + cancelButtonAction: { + viewModel.isShowQuitPopup = false + } + ) + } + + if viewModel.isShowLiveEndPopup { + SodaDialog( + title: "라이브 종료", + desc: "라이브를 종료하시겠습니까?\n" + + "라이브를 종료하면 대화내용은\n" + + "저장되지 않고 사라집니다.\n" + + "참여자들 또한 라이브가 종료되어\n" + + "강제퇴장 됩니다.", + confirmButtonTitle: "예", + confirmButtonAction: { + viewModel.isShowLiveEndPopup = false + viewModel.quitRoom() + }, + cancelButtonTitle: "아니오", + cancelButtonAction: { + viewModel.isShowLiveEndPopup = false + } + ) + } + } + + ZStack { + if viewModel.isShowProfileList, let liveRoomInfo = viewModel.liveRoomInfo { + LiveRoomProfilesDialogView( + isShowing: $viewModel.isShowProfileList, + viewModel: viewModel, + roomInfo: liveRoomInfo, + registerNotification: { viewModel.creatorFollow() }, + unRegisterNotification: { viewModel.creatorUnFollow() }, + onClickProfile: { + if $0 != UserDefaults.int(forKey: .userId) { + viewModel.getUserProfile(userId: $0) + } + }, + onClickNoChatting: { userId, nickname, profileUrl in + viewModel.noChattingUserId = userId + viewModel.noChattingUserNickname = nickname + viewModel.noChattingUserProfileUrl = profileUrl + viewModel.isShowNoChattingConfirm = true + } + ) + } + + if viewModel.isShowUserProfilePopup, let userProfile = viewModel.userProfile { + Color.black.opacity(0.7) + .edgesIgnoringSafeArea(.all) + + LiveRoomUserProfileDialogView( + isShowing: $viewModel.isShowUserProfilePopup, + viewModel: viewModel, + userProfile: userProfile, + onClickSetManager: { + viewModel.setManagerMessageToPeer(userId: $0) + viewModel.setManager(userId: $0) + }, + onClickReleaseManager: { viewModel.changeListener(peerId: $0, isFromManager: true) }, + onClickFollow: { viewModel.creatorFollow(creatorId: $0, isGetUserProfile: true) }, + onClickUnFollow: { viewModel.creatorUnFollow(creatorId: $0, isGetUserProfile: true) }, + onClickInviteSpeaker: { inviteSpeaker(peerId: $0) }, + onClickChangeListener: { + viewModel.changeListener(peerId: $0) + }, + onClickMenu: { userId, userNickname, isBlocked in + viewModel.reportUserId = userId + viewModel.reportUserNickname = userNickname + viewModel.reportUserIsBlocked = isBlocked + viewModel.isShowReportMenu = true + }, + onClickNoChatting: { userId, nickname, profileUrl in + viewModel.noChattingUserId = userId + viewModel.noChattingUserNickname = nickname + viewModel.noChattingUserProfileUrl = profileUrl + viewModel.isShowNoChattingConfirm = true + } + ) + .padding(20) + .popup(isPresented: $viewModel.isShowReportPopup, type: .toast, position: .top, autohideIn: 1.3) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.reportMessage) + .padding(.vertical, 13.3) + .frame(width: geo.size.width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color.button) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + } + } + } + + if viewModel.isShowReportMenu { + VStack(spacing: 0) { + ProfileReportMenuView( + isShowing: $viewModel.isShowReportMenu, + isBlockedUser: viewModel.reportUserIsBlocked, + userBlockAction: { viewModel.isShowUesrBlockConfirm = true }, + userUnBlockAction: { viewModel.userUnBlock() }, + userReportAction: { viewModel.isShowUesrReportView = true }, + profileReportAction: { viewModel.isShowProfileReportConfirm = true } + ) + + Rectangle() + .foregroundColor(Color(hex: "222222")) + .frame(width: screenSize().width, height: 15.3) + } + .ignoresSafeArea() + } + + if viewModel.isShowUesrBlockConfirm { + UserBlockConfirmDialogView( + isShowing: $viewModel.isShowUesrBlockConfirm, + nickname: viewModel.reportUserNickname, + confirmAction: { + viewModel.userBlock { userId in + viewModel.kickOutId = userId + viewModel.kickOut() + } + } + ) + } + + if viewModel.isShowUesrReportView { + UserReportDialogView( + isShowing: $viewModel.isShowUesrReportView, + confirmAction: { reason in + viewModel.report(type: .USER, reason: reason) + } + ) + } + + if viewModel.isShowProfileReportConfirm { + ProfileReportDialogView( + isShowing: $viewModel.isShowProfileReportConfirm, + confirmAction: { + viewModel.report(type: .PROFILE) + } + ) + } + + if viewModel.isShowNoChattingConfirm && viewModel.noChattingUserId > 0 { + LiveRoomNoChattingDialogView( + nickname: viewModel.noChattingUserNickname, + profileUrl: viewModel.noChattingUserProfileUrl, + confirmAction: { + viewModel.isShowNoChattingConfirm = false + viewModel.setNoChatting() + }, + cancelAction: { + viewModel.noChattingUserId = 0 + viewModel.noChattingUserNickname = "" + viewModel.noChattingUserProfileUrl = "" + viewModel.isShowNoChattingConfirm = false + } + ) + } + + if viewModel.isShowPopup { + LiveRoomDialogView( + content: viewModel.popupContent, + cancelTitle: viewModel.popupCancelTitle, + cancelAction: viewModel.popupCancelAction, + confirmTitle: viewModel.popupConfirmTitle, + confirmAction: viewModel.popupConfirmAction + ).onAppear { + if viewModel.popupConfirmTitle == nil && viewModel.popupConfirmAction == nil { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + viewModel.isShowPopup = false + viewModel.popupCancelTitle = nil + viewModel.popupCancelAction = nil + viewModel.popupConfirmTitle = nil + viewModel.popupConfirmAction = nil + } + } + } + } + } + + if viewModel.isShowRouletteSettings { + RouletteSettingsView(isShowing: $viewModel.isShowRouletteSettings) { isActiveRoulette in + self.viewModel.setActiveRoulette(isActiveRoulette: isActiveRoulette) + } + } + + if let preview = viewModel.roulettePreview, viewModel.isShowRoulettePreview { + RoulettePreviewDialog( + isShowing: $viewModel.isShowRoulettePreview, + title: nil, + onClickSpin: { viewModel.spinRoulette() }, + preview: preview + ) + } + + if viewModel.isShowRoulette { + RouletteViewDialog(isShowing: $viewModel.isShowRoulette, options: viewModel.rouletteItems, selectedOption: viewModel.rouletteSelectedItem) { + viewModel.sendRouletteDonation() + } + } + + if viewModel.isLoading && viewModel.liveRoomInfo == nil { + LoadingView() + } + } + .ignoresSafeArea(.keyboard) + .edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init()) + .sheet( + isPresented: $viewModel.isShowShareView, + onDismiss: { viewModel.shareMessage = "" }, + content: { + ActivityViewController(activityItems: [viewModel.shareMessage]) + } + ) + .sheet(isPresented: $viewModel.isShowPhotoPicker) { + ImagePicker( + isShowing: $viewModel.isShowPhotoPicker, + selectedImage: $viewModel.coverImage, + sourceType: .photoLibrary + ) + } + .sheet(isPresented: $viewModel.isShowEditRoomInfoDialog) { + if let liveRoomInfo = viewModel.liveRoomInfo { + LiveRoomInfoEditDialog( + isShowing: $viewModel.isShowEditRoomInfoDialog, + isShowPhotoPicker: $viewModel.isShowPhotoPicker, + viewModel: viewModel, + isLoading: viewModel.isLoading, + currentTitle: liveRoomInfo.title, + currentNotice: liveRoomInfo.notice, + coverImageUrl: liveRoomInfo.coverImageUrl, + coverImage: viewModel.coverImage + ) { newTitle, newNotice in + self.viewModel.editLiveRoomInfo( + title: newTitle, + notice: newNotice + ) + } + } else { + EmptyView() + .onAppear { + viewModel.isShowEditRoomInfoDialog = false + } + } + } + .sheet(isPresented: $viewModel.isShowDonationRankingPopup) { + LiveRoomDonationRankingDialog(isShowing: $viewModel.isShowDonationRankingPopup) + } + .sheet(isPresented: $viewModel.isShowDonationMessagePopup) { + LiveRoomDonationMessageDialog(isShowing: $viewModel.isShowDonationMessagePopup) + } + } + + private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat { + let textView = UITextView(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude)) + textView.font = UIFont.systemFont(ofSize: 11.3) + textView.text = text + return textView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)).height + } + + private func inviteSpeaker(peerId: Int) { + if viewModel.liveRoomInfo!.speakerList.count <= 4 { + viewModel.inviteSpeaker(peerId: peerId) + self.viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요." + self.viewModel.isShowPopup = true + } else { + viewModel.popupContent = "스피커 정원을 초과했습니다." + viewModel.isShowPopup = true + } + } + + private var scrollObservableView: some View { + GeometryReader { proxy in + let offsetY = proxy.frame(in: .global).origin.y + Color.clear + .preference( + key: ScrollOffsetKey.self, + value: offsetY + ) + .onAppear { + viewModel.setOriginOffset(offsetY) + } + } + .frame(height: 0) + } + + struct ScrollOffsetKey: PreferenceKey { + static var defaultValue: CGFloat = .zero + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value += nextValue() + } + } +} + +struct LiveRoomViewV2_Previews: PreviewProvider { + static var previews: some View { + LiveRoomViewV2() + } +} diff --git a/SodaLive/Sources/Main/Home/HomeView.swift b/SodaLive/Sources/Main/Home/HomeView.swift index 169327e..70ed615 100644 --- a/SodaLive/Sources/Main/Home/HomeView.swift +++ b/SodaLive/Sources/Main/Home/HomeView.swift @@ -120,7 +120,7 @@ struct HomeView: View { } if appState.isShowPlayer { - LiveRoomView() + LiveRoomViewV2() } if appState.isShowNotificationSettingsDialog { diff --git a/SodaLive/Sources/UI/Theme/Color.swift b/SodaLive/Sources/UI/Theme/Color.swift new file mode 100644 index 0000000..4ff5ce8 --- /dev/null +++ b/SodaLive/Sources/UI/Theme/Color.swift @@ -0,0 +1,31 @@ +// +// Color.swift +// SodaLive +// +// Created by klaus on 2024/01/17. +// + +import Foundation +import SwiftUI + +extension Color { + static let main = Color(hex: "80D8FF") + static let sub = Color(hex: "1313BC") + static let button = Color(hex: "3bb9f1") + static let bg = Color(hex: "13181B") + static let gray11 = Color(hex: "111111") + static let gray22 = Color(hex: "222222") + static let gray30 = Color(hex: "303030") + static let gray33 = Color(hex: "333333") + static let gray52 = Color(hex: "525252") + static let gray55 = Color(hex: "555555") + static let gray77 = Color(hex: "777777") + static let gray90 = Color(hex: "909090") + static let graybb = Color(hex: "bbbbbb") + static let grayd2 = Color(hex: "d2d2d2") + static let grayee = Color(hex: "eeeeee") + + static let mainRed = Color(hex: "ff5c49") + static let mainRed2 = Color(hex: "ea3a25") + static let mainYellow = Color(hex: "ffdc00") +}