Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/#328] 자기 소개 웹뷰 연결 #348

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public enum BottleWebViewAction: Equatable {
/// 프로필 사진 수정 완료
case profileImageDidChanged

// MARK: - Introduction Setup
/// 자기소개 & 프로필 사진 등록 완료
case introductionDidCompleted

public init?(
type: String,
message: String? = nil,
Expand Down Expand Up @@ -146,6 +150,11 @@ public enum BottleWebViewAction: Equatable {

case "onProfileImageEditComplete":
self = .profileImageDidChanged

// MARK: - Introduction Setup

case "onIntroductionComplete":
self = .introductionDidCompleted

default:
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum BottleWebViewType {
case bottleArrival
case editProfile
case goodFeeling
case introductionSetup
case openURL(url: String)

var path: String {
Expand All @@ -44,6 +45,8 @@ public enum BottleWebViewType {
return "profile/edit"
case .goodFeeling:
return "bottles/sents"
case .introductionSetup:
return "/intro/create"
case .openURL:
return ""
}
Expand All @@ -69,6 +72,9 @@ public enum BottleWebViewType {
case .goodFeeling:
return makeUrlWithToken(path)

case .introductionSetup:
return makeUrlWithToken(path)

case let .openURL(url):
return URL(string: url)!
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import Foundation

import DomainProfileInterface
import DomainProfile
import SharedDesignSystem

import CoreToastInterface
import CoreLoggerInterface

import CoreToastInterface

import ComposableArchitecture

@Reducer
Expand All @@ -24,128 +27,36 @@ public struct IntroductionSetupFeature {

@ObservableState
public struct State: Equatable {
public var introductionText: String
public var textFieldState: TextFieldState
public var keywordItem: [ClipItem]
public var isNextButtonDisable: Bool
public var maxLength: Int
public var isLoading: Bool

public init(
introductionText: String = "",
textFieldState: TextFieldState = .enabled,
keywordItem: [ClipItem] = [],
isNextButtonDisable: Bool = true,
maxLength: Int = 50,
isLoading: Bool = false
) {
self.introductionText = introductionText
self.textFieldState = textFieldState
self.keywordItem = keywordItem
self.isNextButtonDisable = isNextButtonDisable
self.maxLength = maxLength
self.isLoading = isLoading
}
public init() {}
}

public enum Action: BindableAction {
// View Life Cycle
case onLoad

// User Action
case texFieldDidFocused(isFocused: Bool)
case profileSelectDidFatched(ProfileSelect)
case nextButtonDidTapped
case onTapGesture
case backButtonDidTapped

// Delegate
case delegate(Delegate)
case binding(BindingAction<State>)

public enum Delegate {
case nextButtonDidTapped(introductionText: String)
}
public enum Action {
// Web Bridge
case closeWebView
case presentToastDidRequired(message: String)
}

public var body: some ReducerOf<Self> {
BindingReducer()
reducer
}
}

extension IntroductionSetupFeature {
public init() {
@Dependency(\.dismiss) var dismiss
@Dependency(\.toastClient) var toastClient

let reducer = Reduce<State, Action> { state, action in
@Dependency(\.profileClient) var profileClient

switch action {
case .onLoad:
state.isLoading = true
return .run { send in
let profileSelect = try await profileClient.fetchProfileSelect()
await send(.profileSelectDidFatched(profileSelect))
}
case let .texFieldDidFocused(isFocused):
state.textFieldState = isFocused ? .focused : .active
return .none
case .profileSelectDidFatched(let profileSelect):
// TODO: 코드 개선
// TODO: 없으면 ClipItem nil로
state.keywordItem = [
ClipItem(
title: "내 키워드를 참고해보세요",
list: [profileSelect.job, profileSelect.mbti, "\(profileSelect.region.city) \(profileSelect.region.state)", "\(profileSelect.height)", profileSelect.smoke, profileSelect.alcohol]
),

ClipItem(
title: "나의 성격은",
list: profileSelect.keyword
),

ClipItem(
title: "내가 푹 빠진 취미는",
list: (profileSelect.interset.culture ?? [])
+ (profileSelect.interset.entertainment ?? [])
+ (profileSelect.interset.sports ?? [])
+ (profileSelect.interset.etc ?? [])
)
]
state.isLoading = false
return .none
case .binding(\.introductionText):
if state.introductionText.count >= state.maxLength {
state.textFieldState = .focused
state.isNextButtonDisable = false
} else {
state.textFieldState = .error
state.isNextButtonDisable = true
}
return .none

case .nextButtonDidTapped:
return .run { [introductionText = state.introductionText] send in
Log.debug("nextButtonDidTapped")
await send(.delegate(.nextButtonDidTapped(introductionText: introductionText)))
}
case .onTapGesture:
if state.introductionText.count == 0 {
state.textFieldState = .enabled
} else {
state.textFieldState = .active
}
return .none

case .backButtonDidTapped:
case .closeWebView:
return .run { _ in
await dismiss()
await dismiss()
}

case .binding(_):
return .none

case .delegate:
case let .presentToastDidRequired(message):
toastClient.presentToast(message: message)
return .none
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

import SwiftUI

import SharedDesignSystem
import FeatureBaseWebViewInterface

import CoreLoggerInterface

import SharedDesignSystem

import ComposableArchitecture

public struct IntroductionSetupView: View {
Expand All @@ -22,88 +25,28 @@ public struct IntroductionSetupView: View {

public var body: some View {
WithPerceptionTracking {
if store.isLoading {
LoadingIndicator()
} else {
ScrollView {
introductionTitle
introductionTextField
keywordList
nextButton
}.onTapGesture {
store.send(.onTapGesture)
}.setNavigationBar {
makeNaivgationleftButton {
store.send(.backButtonDidTapped)
}
BaseWebView(type: .introductionSetup) { action in
switch action {
case .webViewLoadingDidCompleted:
break

case .closeWebView:
store.send(.closeWebView)

case .introductionDidCompleted:
store.send(.closeWebView)

case let .showTaost(message):
store.send(.presentToastDidRequired(message: message))

default:
Log.assertion(message: "not handled action: \(action)")
}
}
}
.onLoad {
store.send(.onLoad)
}
.scrollIndicators(.hidden)
.ignoresSafeArea(.all, edges: .bottom)
.ignoresSafeArea(.all, edges: [.bottom, .top])
.toolbar(.hidden, for: .bottomBar)
}
}

private extension IntroductionSetupView {
var introductionTitle: some View {
TitleView(
pageInfo: PageInfo(nowPage: 1, totalCount: 2),
title: "보틀에 담을\n소개를 작성해 주세요",
caption: "수정이 어려우니 신중하게 작성해주세요"
)
.padding(.top, .xl)
.padding(.bottom, 32)
.padding(.horizontal, .md)
}

var introductionTextField: some View {
LinesTextField(
textFieldType: .introduction,
textFieldState: $store.textFieldState,
text: $store.introductionText,
placeHolder: "호기심이 많고 새로운 경험을 즐깁니다. 주말엔 책을 읽거나 맛집을 찾아다니며 여유를 즐기고, 친구들과 소소한 모임으로 에너지를 충전해요.",
errorMessage: "최소 \(store.maxLength)글자 이상 작성해주세요",
textLimit: 300
)
.focused($isTextFieldFocused)
.padding(.horizontal, .md)
.padding(.bottom, .sm)
.onChange(of: isTextFieldFocused) { isFocused in
store.send(.texFieldDidFocused(isFocused: isFocused))
}
.onChange(of: store.textFieldState) { textFieldState in
Log.error(textFieldState)
isTextFieldFocused = textFieldState == .active || textFieldState == .enabled ? false : true
}
}

var keywordList: some View {
ClipListContainerView(
clipItemList: store.keywordItem
)
.padding(.horizontal, .md)
.padding(.bottom, 47)
}

var nextButton: some View {
SolidButton(
title: "다음",
sizeType: .full,
buttonType: .throttle,
action: { store.send(.nextButtonDidTapped) }
)
.padding(.horizontal, .md)
.padding(.bottom, .xl)
.disabled(store.isNextButtonDisable)
}
}

extension View {
func endTextEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
.toolbar(.hidden, for: .navigationBar)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,6 @@ extension SandBeachRootFeature {

switch action {

// IntrodctionSetup Delegate
case let .path(.element(id: _, action:
.IntroductionSetup(.delegate(.nextButtonDidTapped(introductionText))))):
state.introduction = introductionText
state.path.append(.ProfileImageUpload(ProfileImageUploadFeature.State()))
return .none

// ProfileImageUpload Delegate
case let .path(.element(id: _, action:
.ProfileImageUpload(.delegate(.doneButtonDidTapped(selectedImageData))))):
Expand Down
Loading