From 5bde76bc6a9488bfa80fd650b45063106b52ffd9 Mon Sep 17 00:00:00 2001 From: HyoWon Choi Date: Tue, 6 Aug 2024 05:34:26 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[=EC=B6=94=EA=B0=80]=20=EC=BF=A0=EB=A7=81?= =?UTF-8?q?=EB=B4=87=20UI=20=EA=B8=B0=EB=B3=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/BotFeatures/BotFeature.swift | 133 ++++++++++++++++++ .../Sources/UIKit/BotUI/BotView.swift | 122 ++++++++++++++++ .../Sources/UIKit/BotUI/Bundle.swift | 12 ++ .../Resources.xcassets/Contents.json | 6 + .../Resources.xcassets/appIcon/Contents.json | 6 + .../kuring_app_circle.imageset/Contents.json | 23 +++ .../kuring_app_circle.imageset/kuring.app.png | Bin 0 -> 1155 bytes .../kuring.app2.png | Bin 0 -> 2455 bytes .../kuring.app3.png | Bin 0 -> 3636 bytes .../kuring_app_gray.imageset/Component 1.png | Bin 0 -> 1219 bytes .../kuring_app_gray.imageset/Component 2.png | Bin 0 -> 2191 bytes .../kuring_app_gray.imageset/Component 3.png | Bin 0 -> 3201 bytes .../kuring_app_gray.imageset/Contents.json | 23 +++ .../Resources.xcassets/icon/Contents.json | 6 + .../icon_info_circle.imageset/Contents.json | 23 +++ .../icon-info-circle-mono.png | Bin 0 -> 481 bytes .../icon-info-circle-mono2.png | Bin 0 -> 961 bytes .../icon-info-circle-mono3.png | Bin 0 -> 1283 bytes .../icon_send_white.imageset/Contents.json | 23 +++ .../icon/icon_send_white.imageset/Send.png | Bin 0 -> 876 bytes .../icon/icon_send_white.imageset/Send2.png | Bin 0 -> 1593 bytes .../icon/icon_send_white.imageset/Send3.png | Bin 0 -> 2348 bytes .../icon/icon_user.imageset/Contents.json | 23 +++ .../icon/icon_user.imageset/Frame 336-2.png | Bin 0 -> 1936 bytes .../icon/icon_user.imageset/Frame 336-3.png | Bin 0 -> 2799 bytes .../icon/icon_user.imageset/Frame 336.png | Bin 0 -> 1032 bytes 26 files changed, 400 insertions(+) create mode 100644 package-kuring/Sources/Features/BotFeatures/BotFeature.swift create mode 100644 package-kuring/Sources/UIKit/BotUI/BotView.swift create mode 100644 package-kuring/Sources/UIKit/BotUI/Bundle.swift create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app2.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app3.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Component 1.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Component 2.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Component 3.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/icon-info-circle-mono.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/icon-info-circle-mono2.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/icon-info-circle-mono3.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Send.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Send2.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Send3.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_user.imageset/Contents.json create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_user.imageset/Frame 336-2.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_user.imageset/Frame 336-3.png create mode 100644 package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_user.imageset/Frame 336.png diff --git a/package-kuring/Sources/Features/BotFeatures/BotFeature.swift b/package-kuring/Sources/Features/BotFeatures/BotFeature.swift new file mode 100644 index 00000000..ce1db9fe --- /dev/null +++ b/package-kuring/Sources/Features/BotFeatures/BotFeature.swift @@ -0,0 +1,133 @@ +// +// File.swift +// +// +// Created by 최효원 on 8/5/24. +// + +import Foundation +import ComposableArchitecture + +@Reducer +public struct BotFeature { + @ObservableState + public struct State: Equatable { + public var chatInfo: ChatInfo = .init() // 현재 입력 중인 질문과 그 답변의 상태를 저장하는 구조체 + public var chatHistory: [ChatInfo] = [] // 이전의 질문과 답변을 저장하는 배열 + public var focus: Field? = .question + + + // 개별 채팅 정보를 나타내는 구조체 + public struct ChatInfo: Equatable { + public var question: String = "" + public var answer: String = "" + public var chatStatus: ChatStatus = .before + + // 채팅 상태를 나타내는 열거형 + public enum ChatStatus { + case before // 질문 전 상태 + case waiting // 질문을 보내고 응답을 기다리는 상태 + case complete // 응답을 성공적으로 받은 상태 + case failure // 응답을 받지 못한 상태 (오류 발생) + } + + // 기본 초기화 함수 + public init( + question: String = "", + answer: String = "", + chatStatus: ChatStatus = .before + ) { + self.question = question + self.answer = answer + self.chatStatus = chatStatus + } + } + + public enum Field { + case question + } + + // 기본 초기화 함수 + public init( + chatInfo: ChatInfo = .init(), + chatHistory: [ChatInfo] = [], + focus: Field? = .question + + ) { + self.chatInfo = chatInfo + self.chatHistory = chatHistory + self.focus = focus + + } + } + + + + // 사용자 동작을 정의하는 열거형 + public enum Action: BindableAction, Equatable { + case sendMessage // 질문을 보내는 액션 + case messageResponse(Result) // 서버 응답을 받는 액션 + case updateQuestion(String) // 사용자가 질문을 입력할 때 상태를 업데이트하는 액션 + case binding(BindingAction) // 상태 변경을 처리하는 액션 + + // 서버 오류를 정의하는 열거형 + public enum ChatError: Error, Equatable { + case serverError(Error) // 서버 오류 + + public static func == (lhs: ChatError, rhs: ChatError) -> Bool { + switch (lhs, rhs) { + case let (.serverError(lhsError), .serverError(rhsError)): + return lhsError.localizedDescription == rhsError.localizedDescription + } + } + } + } + + // 종속성 (Dependencies) + //@Dependency(\.chatService) var chatService 질문을 서버로 보내고 응답을 받는 서비스 + + // 리듀서 본문 + public var body: some ReducerOf { + BindingReducer() // 상태 바인딩을 처리 + + Reduce { state, action in + switch action { + case .binding: + return .none + + case .sendMessage: + guard !state.chatInfo.question.isEmpty else { return .none } // 질문이 비어있지 않은지 확인 + state.focus = nil + state.chatInfo.chatStatus = .waiting // 질문을 보내고 상태를 waiting으로 변경 + return .run { [question = state.chatInfo.question] send in + do { + // let answer = try await chatService.sendQuestion(question) // 질문을 보내고 응답을 기다림 + await send(.messageResponse(.success(question))) // 응답을 성공적으로 받은 경우 + } catch { + await send(.messageResponse(.failure(.serverError(error)))) // 오류가 발생한 경우 + } + } + + case let .messageResponse(.success(answer)): + state.chatInfo.answer = answer // 받은 답변을 상태에 저장 + state.chatInfo.chatStatus = .complete // 상태를 complete로 변경 + state.chatHistory.append(state.chatInfo) // 현재 채팅 정보를 히스토리에 추가 + state.chatInfo = .init() // 현재 채팅 정보를 초기화 + return .none + + case let .messageResponse(.failure(error)): + state.chatInfo.chatStatus = .failure // 상태를 failure로 변경 + print(error.localizedDescription) // 오류 메시지 출력 + return .none + + case let .updateQuestion(question): + state.chatInfo.question = question // 사용자가 입력한 질문을 상태에 저장 + return .none + } + } + } + + public init() { } +} + + diff --git a/package-kuring/Sources/UIKit/BotUI/BotView.swift b/package-kuring/Sources/UIKit/BotUI/BotView.swift new file mode 100644 index 00000000..8cd63c26 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/BotView.swift @@ -0,0 +1,122 @@ +// +// SwiftUIView.swift +// +// +// Created by 최효원 on 8/5/24. +// + +import SwiftUI +import ComposableArchitecture +import ColorSet +import BotFeatures + +public struct BotView: View { + @Bindable var store: StoreOf + @FocusState private var isFocused: Bool + @State private var isShowingPopover = false + + public var body: some View { + ZStack { + Color.Kuring.bg + .ignoresSafeArea() + .onTapGesture { + isFocused = false + } + VStack(alignment: .center) { + HStack { + Button { + + } label: { + Image(systemName: "chevron.backward") + } + .padding() + .frame(width: 20, height: 11) + .foregroundStyle(Color.black) + Spacer() + Text("쿠링봇") + .padding() + .font(.system(size: 18, weight: .semibold)) + Spacer() + + Button { + self.isShowingPopover = true + } label: { + Image("icon_info_circle", bundle: Bundle.bots) + } + .popover( + isPresented: $isShowingPopover, arrowEdge: .top + ) { + Text(" ・ 쿠링봇은 2024년 6월 이후의 공지사항 내용을 기준으로 답변할 수 있어요.\n・ 테스트 기간인 관계로 한 달에 2회까지만 질문 가능해요.") + .font(.system(size: 15, weight: .medium)) + .padding() + } + } + .padding(.horizontal, 18) + Spacer() + Image("kuring_app_gray", bundle: Bundle.bots) + Spacer().frame(height: 20) + Text("궁금한 건국대학교의\n공지 내용을 질문해보세요") + .foregroundStyle(Color.Kuring.caption1) + .font(.system(size: 15, weight: .medium)) + .multilineTextAlignment(.center) + .lineSpacing(5) + Spacer() + HStack(alignment: .bottom, spacing: 12) { + TextField("메세지 입력", text: $store.chatInfo.question.max(), axis: .vertical) + .lineLimit(5) + .focused($isFocused) + .padding(.horizontal) + .padding(.vertical, 12) + .overlay(RoundedRectangle(cornerRadius: 20).strokeBorder(Color.Kuring.gray200, style: StrokeStyle(lineWidth: 1.0))) + + Button { + isFocused = false + + } label: { + Image(systemName: "arrow.up.circle.fill") + .resizable() + .foregroundStyle(Color.Kuring.gray400) + .scaledToFit() + .frame(width: 40, height: 40) + } + } + .padding(.horizontal, 20) + Spacer().frame(height: 8) + Text("쿠링봇은 실수를 할 수 있습니다. 중요한 정보를 확인하세요.") + .foregroundStyle(Color.Kuring.caption2) + .font(.system(size: 12, weight: .medium)) + } + .padding(.bottom, 16) + } + } + + public init(store: StoreOf) { + self.store = store + } +} + +extension View { + func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} + +extension Binding where Value == String { + func max() -> Self { + if self.wrappedValue.count > 200 { + DispatchQueue.main.async { + self.wrappedValue = String(self.wrappedValue.dropLast()) + } + } + return self + } +} + +#Preview { + BotView( + store: Store( + initialState: BotFeature.State(), + reducer: { BotFeature() } + ) + ) +} diff --git a/package-kuring/Sources/UIKit/BotUI/Bundle.swift b/package-kuring/Sources/UIKit/BotUI/Bundle.swift new file mode 100644 index 00000000..7c7f3df2 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Bundle.swift @@ -0,0 +1,12 @@ +// +// File.swift +// +// +// Created by 최효원 on 8/5/24. +// + +import Foundation + +extension Bundle { + public static var bots: Bundle { .module } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/Contents.json new file mode 100644 index 00000000..89d044de --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "kuring.app.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "kuring.app2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "kuring.app3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app.png b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app.png new file mode 100644 index 0000000000000000000000000000000000000000..b54938266c6a41d97f70a3e6ce2e5d2b560797c7 GIT binary patch literal 1155 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 V+cyebG@Aeb002ovPDHLkV1nH1v(5kj literal 0 HcmV?d00001 diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app3.png b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_circle.imageset/kuring.app3.png new file mode 100644 index 0000000000000000000000000000000000000000..cc1e6e8db003929508604e7a9859f6cea3e79955 GIT binary patch literal 3636 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 R@o@kE002ovPDHLkV1nozLF51c literal 0 HcmV?d00001 diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Component 3.png b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Component 3.png new file mode 100644 index 0000000000000000000000000000000000000000..95d6d243ef6cab1f1709cca01134700fe1368dd1 GIT binary patch literal 3201 zcmV-{41V*8P)Ha5K~#7F?VVp% z6U!FHHwFlAmnYW}&>Kf^WCRCBppL*e0&xW52*eQ(Mj(y=9073zT6==xsy%7~e*L;FVS$i0Esw_L1&lauX=&-1jw%oI=>xqVBW}|#YfmqC zhjqEs#RA8t+wHyyFzQZYe0_cW^xnOD`(hB&0{-;r(=$DI+xl>M%3DVI#l2ixTN_9V zfp`}e7Z3E|1Br}|#C;ts_QV*s#3*NHXRlPeJ2G9opr@npE-s(aLUiQal@i1Y^oqCh z@#Du*{x2U)3K)Xak##HU`w{zn`0(M8v

M%KV#`b+CAYQC|#UE`-o8rl()b!9>0? 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 literal 0 HcmV?d00001 diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Contents.json new file mode 100644 index 00000000..7169c0a2 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/appIcon/kuring_app_gray.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/Contents.json new file mode 100644 index 00000000..e4b02db6 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon-info-circle-mono.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon-info-circle-mono2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon-info-circle-mono3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/icon-info-circle-mono.png b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_info_circle.imageset/icon-info-circle-mono.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b283331e62542b9f25a7802334c91254fe5935 GIT binary patch literal 481 zcmV<70UrK|P)QN*nXNO2g3FHo?-a>X=o zUH1(nTO)$1s!~|Glh#^*gD5}s##u=DMH2UTccyYeim>h|#AgJs{?$NvEfPFQ;`{za zE*znpm$kGusctHU%VB1-Bk&|iGPzJ?N?srr z`OJzM+x78}n-83`i$(&tu^GjYnV}|G>a_pO=EFU|d0z4x9=cS71{CxPrKYkO~47gj8Ts0WvVan|X!d$KNtK%>lyb zXR*+XzZs2=)1lk%Zf|dIS45u(7cmk*zu(WM(`jS9UVn|p;}ulZHk(Zr_pH@qhRck_ zVl%Yqq0PVfeBP~At1}9SKm;a}i2*?yxN4fP?mLgg+PMAB=ksmy310*TgTYNQnfwXC zDk{>2!Jz{e3>8@QfPwF&>W} z-2TDU3}ECNcyW_H5SWQ3X5#TwyORXM5MIFziFXNvDZIid5=RNJ&aYOhC%OQ@+}2@g z9c6JOz}AKr6Zx?1XTl6KaqVQC9IX(9|3jA}JR{5I+v&jpfzfF61GgFl2fGO^Yqt#y zHs8E~?$;lv5NvQ?Vu!_)SJjjYs-#&c6skM@=s%dDkbBV4^_9zbZ-;`TG8_&Ky<&$z zB9XWy8NBXSK$X6b03c&y=e?@HE^#WAdL|#2|}+y#_$wiXsB6r-~}u1i(sq<6_Hm z#}_&@Nf!2C+bX29k^pA(n_9%!9D8{GGs(do?6^~<1oV9XN5vk=!jV8Ep%Vxk;`l(= zvf2bVW<$||sR}j$SPN-AJsb4F)(N~&L;#cjUzPQJpi2<}Omx>Ss{+oyTp5XVz;RVo zwh6Fz2i=OoI_T&@;hlW|vv4wy6gDu(E&cZnfl{f|mGWEod_nRs_aoT{JMnV4tdjsT znasT^bAXHlz!B{II>)#W4kCXgg1DH?u9fJJh;sCBpugU)j34Rvjsz@{H3%Xh8VIF!Xy0>^&x%}2Se8R!V0PI8dP(y6O0#kVn^j6O z%?dZBUM`nCN3sJYx~+Wit+4q%GqDLoOTp72 zTK`1m7sU$$k3OucySBDwNDM?S5QTv#r+C>Gz4YP`Dl&lvgF$V1dHED^<3y4rQlTOd z=;7gEe{pf~LgI6!pfJF9H#awXGADBhlp?E9fRx0)4Q(y*+ipJ5``b+{u~sSQ(DTfysStWgLGPt{CBIM??npkxvi>K3WQc|O7S>WRk{S? z;+N*0YcJ$`QByykf@(XjZ{-n$eKh{Yy*fdCm1*vvGUteEi zKe^#S-C78>FQ$Wec0;2iwF0GuQlJ?)yLm%O3~RwTl1_!+CyY&l(t_OFTMN!^{$2_W zG%&1XXOPx<#5Rp!u-vzmtpzNW^`+qOEVi|56UbZ#DRQ889aMLXgW!X|rMUE+6)J&} zngb;cPwzYHTmqT1>LR+Cp-OG=nrfU&@#w26l>(uw>q)`j>3n6qs*=5#&S0%N4(IoX zTtxJ|PIq|D<}oJfh$fbnmRu+GS5{VzMH61uL#CcE!&dAW{#eU};(I!1lxu9eu!CS4 zRY&yD!#enp*PBxXqM4(0c?>4YM}Ma=GfiRYf*F@R(V|C^Qqx$4`i6N=Gx43Pk)tEQfzqP_C9`BW(`UjE;_L>=9SgLp!7LeO z%SbloYG0W?K@;l&Z~IUrwnE0%uZ=LdVq~tA%#6e&Ht2!YluW`e9F0c1X&wUb+Q#E? z=zLWYNm@8Ff_N%EMb`8+z@3%Jzh%zO~6 t|9toJ152>PdpzH9V>2#GDJdyQgufszK6PYkjUxa6002ovPDHLkV1hk?SDXL< literal 0 HcmV?d00001 diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Contents.json b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Contents.json new file mode 100644 index 00000000..e1477fb2 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Send.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Send2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Send3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Send.png b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_send_white.imageset/Send.png new file mode 100644 index 0000000000000000000000000000000000000000..3c8973e65cfcc0c311234ecf56519395e14d9cc5 GIT binary patch literal 876 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 S_I$zs0000Ts6JOTC-1ULYPP$-Zi z7+2t6QWYbDQx)l8$=a2jXr-A)k)yJ-yw%A`l>)^F+C9 zr)srInuh2XoI^^yHjVEQ0;$%|LKX-V36P?AZ9M@mDhV`C#32oM6AK~YMj@+_hB z8UPbwkOcUKWPr0}!w6+YMnio(;NmAY8t0j8&F|7A*{&=(Mw)plWPk?m$Iw1 zH$x*1B97IrvNvL7y$&vMC6Hxp&q1Nxv1Q35O5@^wL4sc0C1>s}%<1;fy&>C>mip z(`$9>8iO#;kxTjE6$g>$*eg#8cYDLKWFSjH02GP^C~R&*xm=ddlgT7RV=+i3lAs-t z0)&ktc&_uE-tOpPLB*Z-YiqE+{u>m9K%Iz2WANpfvoJL^1-j`Lk2(uvl|SfA1k08M zFJHcZLSfU<=TWH3m#;{x(uT;kYj$NuSZ}fU<$W;-^BvvdJbU(|V{zm*<@z?btv19t z({N;864_!wy05Y9N(G)gc`Q%Z3usAKuYL&fNwpn{4J z=hMapXe2?6BeRVs|5^}%o1l?DHhu?H)`r|IA*gX~Uxeeojr<@|*U5Ez`LsK^}8 zK|r7tojP?Iyn>~VW5)t}&rXWDD&6E2xC%YL~{OVMnj)=0Vj73M2Fghxd!_OvzX#+JQ z3i*M#SWeV%KCG|9#-~qvv8ES|9$9nk2easj$q6_ybuy4_S_J2!2-4f4|HWO;%{XOU zi1N;7ExHrI^vpS?`; z$XCH97>xTVP6HGvr^K~M2LF|bqG%XC1Rr3R;oSg;)8OY9KlyJia<+^QQXppAOW=dI z@6<2je!%9U-xhVP7RunG6lmAYGT;F`7)$^M4Sy|al1V1(V|jbmVqV=Wh^5>M_#d~~ zWiabNcHIlv6KL1XT(4&N++YfXcsVvsNVfUjvps>(%{Z`oaES2aVw^8GKW!;@uxb?t zt6fu6G^P^IH?0U0mX<$qoSC0R2I>&sD|SBD%oZ{d#RX z!3lSC%ebSySUetE6#0>o8}$bP;aYd&v6!PfC|!*$Y;9S$2_*|K#GvIk6$f!W*HJ2! zt^*0(2OLowA}}CR5l=B>xY^Fr;7g&flcENHJ}fCfeJRJ)Du6E*K=2PS`|p`yxcbK= zu%735`$ibwqf_W|oQ4+$&6?0^D6ide>r(9~n=KQUCf|$LbhF<=ahce%d#oB;?3DmI zMd|RJj7{O{Y(IojEldM#(~0u-DtU{8?kRpE5?S_;vFMUYVfOY8nn9}yg=%S4V#$#d zAgek>X(r(L)_SdpF)}UI&lJvRa~wnFkm&d|fgry=a8;~CAAvzO6oQOxN3*&`Y33g@ W4{CFA3VToh0000)P)C3U%q;L!#U3-BMQW;MA}XH6WaGR+$P*Ypfz1=xeu9uEn8{|c znUpn~m{i3vW4mhbVgMJUDrgZ2bOk9%_jdZ<;(`DPeRbd4SKMC}$bhVm``0;r`t&*7 zj6%l2!NGLB?&LXR(~Qw|;w;OFPZOssV?0a5vM2usFLTby#Hh>&w|tXxde1nmT2^8+ zoldS&h+t$eob#-0|8hrMU1r$smjn%nXq_(G+idJiSZ?5U5~>F7E*+XHk!AWtWr4;b0+r-VO7&K%7`(JaqDv-T!A!VktU@3tL#7Fkkrbm;uu7%+ z$mQ%Ap05A$xWIc{4d1 z(Fsnp>NaZg!-_VPV3An;p+dBQ1&hQQjA2C^NU%t(;TT4=fuck?A&$fv7-1!MVu|EH z6YRbPL+Bnwg9;D{@IgZUzFeYh?>}%ke@YR`u$Z-wPLF-r>-~EcOs1QV!H6csswpSp z)?BY?te#z>%ne3j=^%@1TPl@$y0-CN?$iu2_%s;PF|&};wvG3HWHD0j3+K(JbT#fu3cTCT+hN1iGl1Ccj)cw*Hjd@y>Me@hGu7f zPa>Qq6A=rc^WE0dx(c>uS2s+{yu*T4R$lZh)M+5xtq*Ps>r0bSh-Dd(bDTSGes=B{ zCPR3`#M<88rmw&L*T7%~ zEzTgyPNR0Cla+O}FjeirF1gUDKmOAD>PIB8*wJ|lw=pDyy7a>*#ch;h2#`ty-s%`lSX$slw_ zhMg!GCKwtVTixQ3U}*Fv!>HO-iVz~TxW!|aXqY<$$z*~ePP#=YSkvUpa)%&@^{ZsU z#1F}_Ta<#uVzE3KNG>};eq-E{%OUajZ)BC46pS+~PX>~k@PDB(Yv|&pOU|dbm4b;0 z+8@Y3ve_IZ68^cxR4Oe?2~0$+MoWzjgAVh?FMyhi86VfGCa+GCx$ve-v~Any3f`$} z*T^q!-MUQ%lP!t4vgNw&G#Lyko@Qrnl5X%EaET@pbDX@zSDTW6U%g0 z=)qk#XR#2KUqiINfAWcv3yjCWW94%GPtFI%qU&sLueu?E{AmgiShsrn<~6;0w?_Q} ztGapfCe4bkFu1`JV7HyUN)_t~p=@%%^2-|`Tydw^btxv)LLF0vV4Ow(tYZ~-cBr~< z%Z3PmY(KIkaC@fK)A!5B2zF7>wmDdY9wNa)1_U#9%kPLYl;gw+oA8G!N<{4-s&s9; zO8bXF2I}wIMT*FhAS(2_F)v?E%XPL1EZvyek=Y3*1QVH#vguKW*AiPS7DXiZlMtr& z;+*sDi^vfRmQz#L%xUjZU|6^QFyOs-8@cw-ym`=BdeWFIux;*fKT;I?YfWRFdZ!ddd#aWkI ze7E*L8g;gHZOYQ(4#5e*4&9>C6=;0=%4Y3-p#_X(s@)T> zf><8*zS><6Seqg0Oc9RTXeiy6-nK_~>^e>9XSE+rr*54&2=$C$(#kmRdE@(tV^A}q z3`WbX!G=fMGB?+WJtjq9IU+3GA;X}$Kf0ZDfwqD{Syfi%^>&T2Xn1dETT(^`>uUa~ z1K7wT7EfYfQ^`7+*SO$k!qg9bJ2gdj@BT^I2NIQ0Zo``ETQIO&sXS(6Dc$hW6K;EJ z+xS)HPN2?l=6B8Ns!7G$L7pDN6*jMJPxRa$Cc{k5 zyPj_q(I<>Abqe9z(N{wsGRGo0yhIU7kRg0J-B<2*pc?JnJ$sokX55yuVFHXU(5W?iSgh{K~PVjx72vFbXht`T872JV88kz+jh9EV6#M9)aE>SOb{9P{sJdyh!8 z{uov)Fm#_$BwBB%5DN^w?Sq`7HEQ$GqGxAtZB&Q_ly>sz$nJ-oZrS&KQQmsFyK65p zi$SyteX6?b_-Fj#c=}}3)39jil|@&lmEg}WYVHxaL8P1d$zb$~maU8MLXpf=T{0I2 zkaZX1)tnBofc9>5tf#pw_Qj}M8?4IW&gb3MvxddD?E?-260E6>)Y!)53Wod4ZKR*7 zhyuIe>wXV%Y>PRMMe&~l#$4?mmp%+dHWi9C%r_O)s0P%z)%lgRlG0tz- z*9l`bnUW{m%2NVOJGh4xxa)r9x~b(Lg*tjQWEgU1o|uylRy?yrYQ;FgS}B!yHWsVo zaUfmuj5GBD%8UfDRqTU-?JpXQ^r~qhwPLhj?ZEL_IFSj5{K#WS#MsAix&;^01x8tR zYT6*jp60HrLKxb_E$8$eo1!HWvBKDx|AE6W{0dt!FYgs1=K=r#002ovPDHLkV1jTM BT&w^9 literal 0 HcmV?d00001 diff --git a/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_user.imageset/Frame 336.png b/package-kuring/Sources/UIKit/BotUI/Resources/Resources.xcassets/icon/icon_user.imageset/Frame 336.png new file mode 100644 index 0000000000000000000000000000000000000000..afd5ea9fdd5c5dfd555cfa94ab71c27ffb9cc60b GIT binary patch literal 1032 zcmV+j1o!)iP)T}?NTpKAR;%?{?7=azAdg6C08Bfz zR3yM7;}0F#1+H5SkiSp?PumWLeftz>()9Ej6*lz z(dz3F(_a3CUSewX4voq!L^VyBoTmRG#_kNV)k|x=b^4kFnPz2CRZOi~1-+=l>Dj6M ztO8HZcyHgG#g^Y3oMnwG5E9w>-u@o!@9jaY_D{&8ksM4;J_8jH#B?eZFF4PxAhUd^ zGCH3>eS|X}6)y$`GVpTd6@-TUsXv)`C?D-9vqwik%)!BduNaA}lQyB@zHJSzAVgdM z1r85>cEqL$dlz6qa+z~@SBf{~vKhjr3~EqbXDy;yug^jk=R-O2+~+G z$^;QOIXMOn|Mqhxld)q^6e0`iG`(DkT+%8p$$jgW9GTy`9Rl1Xo=7C@snYIgaR0#| zWYkBih@}#585quwq>$b1?Y2?65h?EZiz)UiEtEYWr@QARbCo0YT#)hZ&Nl3DxeZT7 zpTa1YKtTi_1aBT&L55MpxbN?CW)O(Lg0N~m8|`e$rWr|$%lB_z?Y7!s)o?>-noJ?7 z;Agb68(oY`yHS$i#Yl^1Byvi!6K$+Hq69(frK4h}*mkeh8T z_t>*e_U)e6t}{l%F!BUq)9VEpYtBy3;Fx2N^n+s2|DQG@<5HTp5*d$%hKKw!0HXOD zf+XjL@t*VBP|2DMhE>k18}n&LMDkip7zk+jZYufK{oW)<9!W%RY!jhN2gFt}JXKaL zKA~w?bbo6)2603!T9@LrpFee83{E$Sxgp3A6}Jt$DT>?Pa>U#`N_$>5IHLYc@I}Ki zbXpAi3{R7%7)juDj69x@5Q}`rlZ6P#5f@aSPo+|Yc6Le^R@~@ Date: Wed, 7 Aug 2024 06:48:52 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[2.1.0]=20=EC=BF=A0=EB=A7=81=EB=B4=87=20UI?= =?UTF-8?q?=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KuringApp/KuringApp/ContentView.swift | 5 - package-kuring/Package.swift | 18 ++ .../Features/BotFeatures/BotFeature.swift | 116 +++++----- .../Sources/UIKit/BotUI/BotView.swift | 205 ++++++++++++------ .../Sources/UIKit/BotUI/ChatView.swift | 123 +++++++++++ .../Sources/UIKit/BotUI/SendPopup.swift | 71 ++++++ .../Sources/UIKit/PopupUI/PopupView.swift | 18 -- 7 files changed, 402 insertions(+), 154 deletions(-) create mode 100644 package-kuring/Sources/UIKit/BotUI/ChatView.swift create mode 100644 package-kuring/Sources/UIKit/BotUI/SendPopup.swift delete mode 100644 package-kuring/Sources/UIKit/PopupUI/PopupView.swift diff --git a/KuringApp/KuringApp/ContentView.swift b/KuringApp/KuringApp/ContentView.swift index 20a01161..9ed75273 100644 --- a/KuringApp/KuringApp/ContentView.swift +++ b/KuringApp/KuringApp/ContentView.swift @@ -83,11 +83,6 @@ struct ContentView: View { } } .tint(Color.Kuring.gray600) - - /// 팝업 추가 - if PopupView.checkShowPopup() { - PopupView() - } } } } diff --git a/package-kuring/Package.swift b/package-kuring/Package.swift index 47043d82..90e320e1 100644 --- a/package-kuring/Package.swift +++ b/package-kuring/Package.swift @@ -12,6 +12,7 @@ let package = Package( /// ```swift /// import NoticeUI /// ``` + "BotUI", "NoticeUI", "SubscriptionUI", "DepartmentUI", @@ -47,6 +48,15 @@ let package = Package( ], targets: [ // MARK: App Library Dependencies + .target( + name: "BotUI", + dependencies: [ + "ColorSet", "BotFeatures", + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + ], + path: "Sources/UIKit/BotUI", + resources: [.process("Resources")] + ), .target( name: "NoticeUI", dependencies: [ @@ -138,6 +148,14 @@ let package = Package( ), // MARK: Features + .target( + name: "BotFeatures", + dependencies: [ + "Networks", + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + ], + path: "Sources/Features/BotFeatures" + ), .target( name: "NoticeFeatures", dependencies: [ diff --git a/package-kuring/Sources/Features/BotFeatures/BotFeature.swift b/package-kuring/Sources/Features/BotFeatures/BotFeature.swift index ce1db9fe..8cc7f2cc 100644 --- a/package-kuring/Sources/Features/BotFeatures/BotFeature.swift +++ b/package-kuring/Sources/Features/BotFeatures/BotFeature.swift @@ -12,33 +12,41 @@ import ComposableArchitecture public struct BotFeature { @ObservableState public struct State: Equatable { - public var chatInfo: ChatInfo = .init() // 현재 입력 중인 질문과 그 답변의 상태를 저장하는 구조체 - public var chatHistory: [ChatInfo] = [] // 이전의 질문과 답변을 저장하는 배열 + public var chatInfo: ChatInfo = .init() + public var chatHistory: [ChatInfo] = [] public var focus: Field? = .question - - // 개별 채팅 정보를 나타내는 구조체 public struct ChatInfo: Equatable { - public var question: String = "" - public var answer: String = "" + public var limit: Int = 2 + public var text: String = "" + public var messageType: MessageType = .question public var chatStatus: ChatStatus = .before - - // 채팅 상태를 나타내는 열거형 + + public enum MessageType: String, Equatable { + case question + case answer + } + public enum ChatStatus { - case before // 질문 전 상태 - case waiting // 질문을 보내고 응답을 기다리는 상태 - case complete // 응답을 성공적으로 받은 상태 - case failure // 응답을 받지 못한 상태 (오류 발생) + /// 질문 보내기 전 + case before + /// 질문 보낸 후 + case waiting + /// 답변 완료 + case complete + /// 답변 실패 + case failure } - - // 기본 초기화 함수 + public init( - question: String = "", - answer: String = "", + limit: Int = 2, + text: String = "", + messageType: MessageType = .question, chatStatus: ChatStatus = .before ) { - self.question = question - self.answer = answer + self.limit = limit + self.text = text + self.messageType = messageType self.chatStatus = chatStatus } } @@ -46,34 +54,27 @@ public struct BotFeature { public enum Field { case question } - - // 기본 초기화 함수 + public init( chatInfo: ChatInfo = .init(), chatHistory: [ChatInfo] = [], focus: Field? = .question - ) { self.chatInfo = chatInfo self.chatHistory = chatHistory self.focus = focus - } } - - - // 사용자 동작을 정의하는 열거형 public enum Action: BindableAction, Equatable { - case sendMessage // 질문을 보내는 액션 - case messageResponse(Result) // 서버 응답을 받는 액션 - case updateQuestion(String) // 사용자가 질문을 입력할 때 상태를 업데이트하는 액션 - case binding(BindingAction) // 상태 변경을 처리하는 액션 - - // 서버 오류를 정의하는 열거형 + case sendMessage + case messageResponse(Result) + case updateQuestion(String) + case binding(BindingAction) + public enum ChatError: Error, Equatable { - case serverError(Error) // 서버 오류 - + case serverError(Error) + public static func == (lhs: ChatError, rhs: ChatError) -> Bool { switch (lhs, rhs) { case let (.serverError(lhsError), .serverError(rhsError)): @@ -82,52 +83,47 @@ public struct BotFeature { } } } - - // 종속성 (Dependencies) - //@Dependency(\.chatService) var chatService 질문을 서버로 보내고 응답을 받는 서비스 - - // 리듀서 본문 + public var body: some ReducerOf { - BindingReducer() // 상태 바인딩을 처리 - + BindingReducer() + Reduce { state, action in switch action { case .binding: return .none - + case .sendMessage: - guard !state.chatInfo.question.isEmpty else { return .none } // 질문이 비어있지 않은지 확인 + guard !state.chatInfo.text.isEmpty else { return .none } state.focus = nil - state.chatInfo.chatStatus = .waiting // 질문을 보내고 상태를 waiting으로 변경 - return .run { [question = state.chatInfo.question] send in + state.chatInfo.chatStatus = .waiting + return .run { [question = state.chatInfo.text] send in do { - // let answer = try await chatService.sendQuestion(question) // 질문을 보내고 응답을 기다림 - await send(.messageResponse(.success(question))) // 응답을 성공적으로 받은 경우 + // let answer = try await chatService.sendQuestion(question) + /// 임시 응답 + await send(.messageResponse(.success(question))) } catch { - await send(.messageResponse(.failure(.serverError(error)))) // 오류가 발생한 경우 + await send(.messageResponse(.failure(.serverError(error)))) } } - + case let .messageResponse(.success(answer)): - state.chatInfo.answer = answer // 받은 답변을 상태에 저장 - state.chatInfo.chatStatus = .complete // 상태를 complete로 변경 - state.chatHistory.append(state.chatInfo) // 현재 채팅 정보를 히스토리에 추가 - state.chatInfo = .init() // 현재 채팅 정보를 초기화 + state.chatInfo.text = answer + state.chatInfo.chatStatus = .complete + state.chatHistory.append(state.chatInfo) + state.chatInfo = .init() return .none - + case let .messageResponse(.failure(error)): - state.chatInfo.chatStatus = .failure // 상태를 failure로 변경 - print(error.localizedDescription) // 오류 메시지 출력 + state.chatInfo.chatStatus = .failure + print(error.localizedDescription) return .none - + case let .updateQuestion(question): - state.chatInfo.question = question // 사용자가 입력한 질문을 상태에 저장 + state.chatInfo.text = question return .none } } } - + public init() { } } - - diff --git a/package-kuring/Sources/UIKit/BotUI/BotView.swift b/package-kuring/Sources/UIKit/BotUI/BotView.swift index 8cd63c26..659e3ef6 100644 --- a/package-kuring/Sources/UIKit/BotUI/BotView.swift +++ b/package-kuring/Sources/UIKit/BotUI/BotView.swift @@ -9,84 +9,144 @@ import SwiftUI import ComposableArchitecture import ColorSet import BotFeatures +import TipKit public struct BotView: View { @Bindable var store: StoreOf - @FocusState private var isFocused: Bool - @State private var isShowingPopover = false + @FocusState private var isInputFocused: Bool + @State private var isPopoverVisible = false + @State private var isSendPopupVisible = false + @State private var messageCountRemaining = 2 + @State private var chatMessages: [Message] = [] public var body: some View { ZStack { Color.Kuring.bg .ignoresSafeArea() .onTapGesture { - isFocused = false + isInputFocused = false } + VStack(alignment: .center) { - HStack { - Button { - - } label: { - Image(systemName: "chevron.backward") - } - .padding() - .frame(width: 20, height: 11) - .foregroundStyle(Color.black) - Spacer() - Text("쿠링봇") - .padding() - .font(.system(size: 18, weight: .semibold)) - Spacer() - - Button { - self.isShowingPopover = true - } label: { - Image("icon_info_circle", bundle: Bundle.bots) - } - .popover( - isPresented: $isShowingPopover, arrowEdge: .top - ) { - Text(" ・ 쿠링봇은 2024년 6월 이후의 공지사항 내용을 기준으로 답변할 수 있어요.\n・ 테스트 기간인 관계로 한 달에 2회까지만 질문 가능해요.") - .font(.system(size: 15, weight: .medium)) - .padding() - } - } - .padding(.horizontal, 18) - Spacer() - Image("kuring_app_gray", bundle: Bundle.bots) - Spacer().frame(height: 20) - Text("궁금한 건국대학교의\n공지 내용을 질문해보세요") - .foregroundStyle(Color.Kuring.caption1) - .font(.system(size: 15, weight: .medium)) - .multilineTextAlignment(.center) - .lineSpacing(5) - Spacer() - HStack(alignment: .bottom, spacing: 12) { - TextField("메세지 입력", text: $store.chatInfo.question.max(), axis: .vertical) - .lineLimit(5) - .focused($isFocused) - .padding(.horizontal) - .padding(.vertical, 12) - .overlay(RoundedRectangle(cornerRadius: 20).strokeBorder(Color.Kuring.gray200, style: StrokeStyle(lineWidth: 1.0))) - - Button { - isFocused = false - - } label: { - Image(systemName: "arrow.up.circle.fill") - .resizable() - .foregroundStyle(Color.Kuring.gray400) - .scaledToFit() - .frame(width: 40, height: 40) - } - } - .padding(.horizontal, 20) - Spacer().frame(height: 8) - Text("쿠링봇은 실수를 할 수 있습니다. 중요한 정보를 확인하세요.") - .foregroundStyle(Color.Kuring.caption2) - .font(.system(size: 12, weight: .medium)) + headerView + chatView + inputView + infoText } .padding(.bottom, 16) + + if isSendPopupVisible { + sendPopup + .transition(.opacity) + .zIndex(1) + } + } + } + + private var headerView: some View { + HStack { + backButton + Spacer() + titleText + Spacer() + infoButton + } + .padding(.horizontal, 18) + } + + private var backButton: some View { + Button { + // 뒤로 가기 버튼 동작 구현 + } label: { + Image(systemName: "chevron.backward") + .padding() + .frame(width: 20, height: 11) + .foregroundStyle(Color.black) + } + } + + private var titleText: some View { + Text("쿠링봇") + .padding() + .font(.system(size: 18, weight: .semibold)) + } + + private var infoButton: some View { + Button { + isPopoverVisible.toggle() + } label: { + Image("icon_info_circle", bundle: Bundle.bots) + } + .popover(isPresented: $isPopoverVisible, arrowEdge: .top) { + popoverContent + } + } + + private var popoverContent: some View { + VStack(spacing: 10) { + Text("• 쿠링봇은 2024년 6월 이후의 공지\n 사항 내용을 기준으로 답변할 수 있\n 어요.") + Text("• 테스트 기간인 관계로 한 달에 2회\n 까지만 질문 가능해요.") + } + .lineSpacing(5) + .font(.system(size: 15, weight: .medium)) + .padding(20) + .presentationBackground(Color.Kuring.gray100) + .presentationCompactAdaptation(.popover) + } + + private var chatView: some View { + if !chatMessages.isEmpty { + AnyView(ChatView(messages: chatMessages)) + } else { + AnyView(ChatEmptyView()) + } + } + + private var inputView: some View { + HStack(alignment: .bottom, spacing: 12) { + TextField("질문을 입력해주세요", text: $store.chatInfo.question.limit(to: 300), axis: .vertical) + .lineLimit(5) + .focused($isInputFocused) + .padding(.horizontal) + .padding(.vertical, 12) + .overlay(RoundedRectangle(cornerRadius: 20).strokeBorder(Color.Kuring.gray200, style: StrokeStyle(lineWidth: 1.0))) + .disabled(messageCountRemaining == 0) + + sendButton + } + .padding(.horizontal, 20) + .disabled(messageCountRemaining == 0) + } + + private var sendButton: some View { + Button { + isInputFocused = false + isSendPopupVisible = true + } label: { + Image(systemName: "arrow.up.circle.fill") + .resizable() + .foregroundStyle(Color.Kuring.gray400) + .scaledToFit() + .frame(width: 40, height: 40) + } + } + + private var infoText: some View { + Text("쿠링봇은 실수를 할 수 있습니다. 중요한 정보를 확인하세요.") + .foregroundStyle(Color.Kuring.caption2) + .font(.system(size: 12, weight: .medium)) + .padding(.top, 8) + } + + private var sendPopup: some View { + SendPopup(isVisible: $isSendPopupVisible) { + if messageCountRemaining > 0 { + messageCountRemaining -= 1 + let userMessage = Message(text: store.chatInfo.question, type: .question, sendCount: messageCountRemaining) + let botResponse = Message(text: "자동 응답입니다.", type: .answer, sendCount: messageCountRemaining) + chatMessages.append(contentsOf: [userMessage, botResponse]) + store.chatInfo.question = "" + } } } @@ -95,23 +155,26 @@ public struct BotView: View { } } -extension View { - func hideKeyboard() { - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) +struct InfoTip: Tip { + var title: Text { + Text(" ・ 쿠링봇은 2024년 6월 이후의 공지사항 내용을 기준으로 답변할 수 있어요.\n") + + Text(" ・ 테스트 기간인 관계로 한 달에 2회까지만 질문 가능해요.") } } +/// 글자 수 max 판단 extension Binding where Value == String { - func max() -> Self { - if self.wrappedValue.count > 200 { + func limit(to maxLength: Int) -> Self { + if self.wrappedValue.count > maxLength { DispatchQueue.main.async { - self.wrappedValue = String(self.wrappedValue.dropLast()) + self.wrappedValue = String(self.wrappedValue.prefix(maxLength)) } } return self } } + #Preview { BotView( store: Store( diff --git a/package-kuring/Sources/UIKit/BotUI/ChatView.swift b/package-kuring/Sources/UIKit/BotUI/ChatView.swift new file mode 100644 index 00000000..a058a552 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/ChatView.swift @@ -0,0 +1,123 @@ +// +// SwiftUIView.swift +// +// +// Created by 최효원 on 8/6/24. +// + +import SwiftUI +import ColorSet +import ComposableArchitecture + +enum MessageType: Hashable { + case question + case answer + + var backgroundColor: Color { + switch self { + case .question: return Color.Kuring.gray100 + case .answer: return Color.Kuring.primarySelected + } + } + + var image: Image { + switch self { + case .question: + return Image(systemName: "person.circle.fill") + case .answer: + return Image("kuring_app_circle", bundle: Bundle.bots) + } + } +} + +struct ChatView: View { + var messages: [Message] + + var body: some View { + VStack(alignment: .center, spacing: 16) { + ForEach(messages, id: \.self) { message in + ChatRowView(message: message) + if message.type == .answer { + possibleCountText(for: message.sendCount) + } + } + Spacer() + } + .padding(.bottom, 5) + } + + private func possibleCountText(for sendCount: Int) -> some View { + let currentDate = formattedCurrentDate() + return Text("질문 가능 횟수 \(sendCount)회 (\(currentDate) 기준)") + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(sendCount == 0 ? Color.Kuring.warning : Color.Kuring.caption1) + } + + private func formattedCurrentDate() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy.MM.dd" + return dateFormatter.string(from: Date()) + } +} + +struct ChatRowView: View { + var message: Message + + var body: some View { + HStack(alignment: .top) { + if message.type == .question { + Spacer() + messageBubble + userImage + } else { + userImage + messageBubble + Spacer() + } + } + .padding(message.type == .question ? .trailing : .leading, 16) + } + + private var messageBubble: some View { + let maxWidth = UIScreen.main.bounds.width * 0.7 + + return Text(message.text) + .padding() + .background(message.type.backgroundColor) + .cornerRadius(16) + .frame(maxWidth: maxWidth, alignment: message.type == .question ? .trailing : .leading) + .fixedSize(horizontal: false, vertical: true) + } + + private var userImage: some View { + message.type.image + .resizable() + .scaledToFill() + .frame(width: 36, height: 36) + .clipShape(Circle()) + .foregroundStyle(Color.Kuring.gray300, Color.Kuring.gray100) + .overlay(Circle().stroke(Color.Kuring.gray300, lineWidth: 0.1)) + } +} + +struct ChatEmptyView: View { + var body: some View { + VStack { + Spacer() + Image("kuring_app_gray", bundle: Bundle.bots) + Spacer().frame(height: 20) + Text("궁금한 건국대학교의\n공지 내용을 질문해보세요") + .foregroundStyle(Color.Kuring.caption1) + .font(.system(size: 15, weight: .medium)) + .multilineTextAlignment(.center) + .lineSpacing(5) + Spacer() + } + } +} + +struct Message: Hashable { + var text: String + var type: MessageType + var sendCount: Int +} diff --git a/package-kuring/Sources/UIKit/BotUI/SendPopup.swift b/package-kuring/Sources/UIKit/BotUI/SendPopup.swift new file mode 100644 index 00000000..bae4d190 --- /dev/null +++ b/package-kuring/Sources/UIKit/BotUI/SendPopup.swift @@ -0,0 +1,71 @@ +// +// Copyright (c) 2024 쿠링 +// See the 'License.txt' file for licensing information. +// + +import SwiftUI +import ComposableArchitecture +import ColorSet + +struct SendPopup: View { + @Binding var isVisible: Bool + var onSendAction: () -> Void + + var body: some View { + ZStack { + if isVisible { + Color.black.opacity(0.3) + .edgesIgnoringSafeArea(.all) + } + + VStack(spacing: 0) { + confirmationMessage + actionButtons + } + .background(Color.Kuring.bg) + .cornerRadius(15) + .padding(.horizontal, 45) + .frame(maxHeight: .infinity) + .font(.system(size: 16, weight: .medium)) + } + } + + private var confirmationMessage: some View { + Text("전송하시면 횟수 차감이 인정돼요.\n전송할까요?") + .foregroundStyle(Color.Kuring.body) + .multilineTextAlignment(.center) + .padding(40) + .padding(.bottom, 0) + } + + private var actionButtons: some View { + VStack(spacing: 0) { + Divider() + HStack(alignment: .center) { + cancelButton + Divider().padding(.horizontal, 40) + sendButton + } + .frame(height: 55) + } + } + + private var cancelButton: some View { + Button { + isVisible = false + } label: { + Text("취소하기") + .foregroundStyle(Color.Kuring.body) + } + } + + private var sendButton: some View { + Button { + isVisible = false + onSendAction() + } label: { + Text("전송하기") + .foregroundStyle(Color.Kuring.primary) + } + } +} diff --git a/package-kuring/Sources/UIKit/PopupUI/PopupView.swift b/package-kuring/Sources/UIKit/PopupUI/PopupView.swift deleted file mode 100644 index 861e15c1..00000000 --- a/package-kuring/Sources/UIKit/PopupUI/PopupView.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// SwiftUIView.swift -// -// -// Created by 최효원 on 8/1/24. -// - -import SwiftUI - -struct SwiftUIView: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} - -#Preview { - SwiftUIView() -} From 5b1ce135a938081fc7b08890b4cc7093234e5903 Mon Sep 17 00:00:00 2001 From: HyoWon Choi Date: Wed, 7 Aug 2024 17:45:40 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[2.1.0]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-kuring/Sources/UIKit/BotUI/BotView.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/package-kuring/Sources/UIKit/BotUI/BotView.swift b/package-kuring/Sources/UIKit/BotUI/BotView.swift index 659e3ef6..fc2a205a 100644 --- a/package-kuring/Sources/UIKit/BotUI/BotView.swift +++ b/package-kuring/Sources/UIKit/BotUI/BotView.swift @@ -9,7 +9,6 @@ import SwiftUI import ComposableArchitecture import ColorSet import BotFeatures -import TipKit public struct BotView: View { @Bindable var store: StoreOf @@ -155,13 +154,6 @@ public struct BotView: View { } } -struct InfoTip: Tip { - var title: Text { - Text(" ・ 쿠링봇은 2024년 6월 이후의 공지사항 내용을 기준으로 답변할 수 있어요.\n") - + Text(" ・ 테스트 기간인 관계로 한 달에 2회까지만 질문 가능해요.") - } -} - /// 글자 수 max 판단 extension Binding where Value == String { func limit(to maxLength: Int) -> Self {