Skip to content

Commit

Permalink
[추가] 쿠링봇 UI 기본 로직 추가 (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
wonniiii committed Aug 5, 2024
1 parent 1456253 commit 5bde76b
Show file tree
Hide file tree
Showing 26 changed files with 400 additions and 0 deletions.
133 changes: 133 additions & 0 deletions package-kuring/Sources/Features/BotFeatures/BotFeature.swift
Original file line number Diff line number Diff line change
@@ -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<String, ChatError>) // 서버 응답을 받는 액션
case updateQuestion(String) // 사용자가 질문을 입력할 때 상태를 업데이트하는 액션
case binding(BindingAction<State>) // 상태 변경을 처리하는 액션

// 서버 오류를 정의하는 열거형
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<Self> {
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() { }
}


122 changes: 122 additions & 0 deletions package-kuring/Sources/UIKit/BotUI/BotView.swift
Original file line number Diff line number Diff line change
@@ -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<BotFeature>
@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<BotFeature>) {
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() }
)
)
}
12 changes: 12 additions & 0 deletions package-kuring/Sources/UIKit/BotUI/Bundle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// File.swift
//
//
// Created by 최효원 on 8/5/24.
//

import Foundation

extension Bundle {
public static var bots: Bundle { .module }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 5bde76b

Please sign in to comment.