Skip to content
This repository has been archived by the owner on Aug 14, 2024. It is now read-only.

Commit

Permalink
[WEAV-342] 앱 개선사항 제안 기능 추가 (#70)
Browse files Browse the repository at this point in the history
* [Add] 개선제안 뷰 틀 구현

* [Design] WeaveTextView 구현

* [Add] Suggestion Feature 액션 구조 생성

* [Add] 개선사항 제안 기능 구현

* [Design] Empty View multiline setting

* [Add] 뒤로가기 액션 추가
  • Loading branch information
jisu15-kim authored Apr 25, 2024
1 parent 6e06c79 commit 950a38e
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// AppSuggestionFeature.swift
// Weave-ios
//
// Created by 김지수 on 4/22/24.
//

import Foundation
import ComposableArchitecture
import Services
import CoreKit

struct AppSuggestionFeature: Reducer {
@Dependency(\.coordinator) var appCoordinator
@Dependency(\.dismiss) var dismiss

struct State: Equatable {
@BindingState var inputText: String = ""
@BindingState var isShowCompleteAlert: Bool = false
}

enum Action: BindableAction {
case didTappedDismiss
case didTappedSummitButton
case requestSuggestionText
case replaceInputText(text: String)
case didSuccessedSummit
case didTappedUserCompleteButton
case binding(BindingAction<State>)
}

var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .didTappedDismiss:
return .run { send in
await dismiss()
}
case .replaceInputText(let text):
state.inputText = text
return .none
case .didTappedSummitButton:
return .send(.requestSuggestionText)
case .requestSuggestionText:
return .run { [text = state.inputText] send in
try await requestSuggestionText(text: text)
await send.callAsFunction(.didSuccessedSummit)
} catch: { error, send in
print(error)
}
case .didSuccessedSummit:
state.isShowCompleteAlert = true
return .none
case .didTappedUserCompleteButton:
return .run { send in
await dismiss()
}
case .binding(_):
return .none
}
}
}

func requestSuggestionText(text: String) async throws {
let endPoint = APIEndpoints.appSuggestionRequest(text: text)
let provider = APIProvider()
try await provider.requestWithNoResponse(with: endPoint, successCode: 201)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct SettingFeautre: Reducer {
@BindingState var isShowLogoutAlert: Bool = false
@BindingState var isShowUnregisterAlert: Bool = false
@BindingState var isShowPasteSuccessAlert: Bool = false
@PresentationState var destination: Destination.State?
}

enum Action: BindableAction {
Expand All @@ -27,6 +28,7 @@ struct SettingFeautre: Reducer {
case showUnregisterAlert
case resignSuccessed
case binding(BindingAction<State>)
case destination(PresentationAction<Destination.Action>)
}

var body: some ReducerOf<Self> {
Expand All @@ -48,6 +50,8 @@ struct SettingFeautre: Reducer {
state.isShowLogoutAlert = true
case .unregister:
state.isShowUnregisterAlert = true
case .appSuggestion:
state.destination = .appSuggestion(.init())
}
return .none
case .showLogoutAlert:
Expand Down Expand Up @@ -80,8 +84,13 @@ struct SettingFeautre: Reducer {

case .binding(_):
return .none
default:
return .none
}
}
.ifLet(\.$destination, action: /Action.destination) {
Destination()
}
}

private func requestLogout() async throws {
Expand All @@ -101,3 +110,20 @@ struct SettingFeautre: Reducer {
UDManager.refreshToken = ""
}
}

//MARK: - Destination
extension SettingFeautre {
struct Destination: Reducer {
enum State: Equatable {
case appSuggestion(AppSuggestionFeature.State)
}
enum Action {
case appSuggestion(AppSuggestionFeature.Action)
}
var body: some ReducerOf<Self> {
Scope(state: /State.appSuggestion, action: /Action.appSuggestion) {
AppSuggestionFeature()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// AppSuggestionRequestDTO.swift
// Weave-ios
//
// Created by 김지수 on 4/23/24.
//

import Foundation
import Services
import CoreKit

struct AppSuggestionRequestDTO: Codable {
let contents: String
}

extension APIEndpoints {
static func appSuggestionRequest(text: String) -> EndPoint<EmptyResponse> {
let requestDTO = AppSuggestionRequestDTO(contents: text)
return EndPoint(
path: "api/suggestions",
method: .post,
bodyParameters: requestDTO,
headers: [
"Authorization": "Bearer \(UDManager.accessToken)"
]
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import Foundation
enum SettingCategoryTypes: CaseIterable {
case policies
case account
case user

var headerTitle: String {
switch self {
case .policies:
return "약관 및 정책"
case .account:
return "계정"
case .user:
return "고객"
}
}

Expand All @@ -33,6 +36,10 @@ enum SettingCategoryTypes: CaseIterable {
.logout,
.unregister
]
case .user:
return [
.appSuggestion
]
}
}

Expand All @@ -42,6 +49,7 @@ enum SettingCategoryTypes: CaseIterable {
case myID
case logout
case unregister
case appSuggestion

var title: String {
switch self {
Expand All @@ -50,6 +58,7 @@ enum SettingCategoryTypes: CaseIterable {
case .myID: return "내 ID"
case .logout: return "로그아웃"
case .unregister: return "회원 탈퇴"
case .appSuggestion: return "위브 개선 제안"
}
}

Expand All @@ -61,7 +70,7 @@ enum SettingCategoryTypes: CaseIterable {
case .privacyPolicy:
guard let url = URL(string: "https://weave-org.notion.site/WEAVE-a65c3e3a483e4ec1bcc94353b21f771b") else { return nil }
return url
case .myID, .logout, .unregister: return nil
default: return nil
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// AppSuggestionView.swift
// Weave-ios
//
// Created by 김지수 on 4/22/24.
//

import SwiftUI
import DesignSystem
import ComposableArchitecture

struct AppSuggestionView: View {
let store: StoreOf<AppSuggestionFeature>
let textLimit = 2000
@State var isShowTextLimitAlert: Bool = false

var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack {
ScrollView {
VStack {
VStack(spacing: 12) {
Text("개선할 점을 알려주세요")
.font(.pretendard(._600, size: 24))
Text("여러분의 의견은 위브에 아주 큰 도움이 돼요!")
.font(.pretendard(._500, size: 16))
}
.multilineTextAlignment(.center)
.padding(.top, 38)
.padding(.bottom, 58)

WeaveTextView(
text: viewStore.$inputText,
placeholder: "의견을 작성해 주세요.",
height: 188
)
.onChange(of: viewStore.inputText) { oldValue, newValue in
if newValue.count > textLimit {
isShowTextLimitAlert = true
let newText = viewStore.inputText.prefix(textLimit)
viewStore.send(.replaceInputText(text: String(newText)))
}
}
}
.padding(.horizontal, 16)
}
WeaveButton(
title: "제출하기",
size: .large,
isEnabled: !viewStore.inputText.isEmpty
) {
viewStore.send(.didTappedSummitButton)
}
.padding(.horizontal, 16)
}
.weaveAlert(
isPresented: $isShowTextLimitAlert,
title: "🙇‍♂️\n최대 2000자까지 작성 가능해요.",
primaryButtonTitle: "확인했어요",
primaryAction: {
viewStore.send(.didTappedUserCompleteButton)
}
)
.weaveAlert(
isPresented: viewStore.$isShowCompleteAlert,
title: "🙇‍♂️\n의견이 정상적으로 제출됐어요.",
message: "소중한 의견 감사합니다!",
primaryButtonTitle: "확인했어요",
primaryAction: {
viewStore.send(.didTappedUserCompleteButton)
}
)
.navigationTitle("위브 개선 제안")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
viewStore.send(.didTappedDismiss)
} label: {
Image(systemName: "chevron.left")
}
.foregroundStyle(.white)
}
}
}
}
}

#Preview {
AppSuggestionView(store: .init(initialState: AppSuggestionFeature.State(), reducer: {
AppSuggestionFeature()
}))
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ struct SettingView: View {
.frame(height: 40)
.padding(.bottom, 60)
}
.navigationDestination(
store: self.store.scope(state: \.$destination, action: { .destination($0) }),
state: /SettingFeautre.Destination.State.appSuggestion,
action: SettingFeautre.Destination.Action.appSuggestion
) { store in
AppSuggestionView(store: store)
}
.padding(.horizontal, 16)
.navigationTitle("설정")
.navigationBarTitleDisplayMode(.inline)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public struct ListEmptyGuideView: View {
VStack(spacing: 10) {
Text(headerTitle)
.font(.pretendard(._600, size: 22))
.multilineTextAlignment(.center)
.lineSpacing(5)
if let subTitle {
Text(subTitle)
Expand All @@ -50,6 +49,7 @@ public struct ListEmptyGuideView: View {
.padding(.horizontal, 80)
}
}
.multilineTextAlignment(.center)
.frame(height: viewSize.height)
}
}
Loading

0 comments on commit 950a38e

Please sign in to comment.