Skip to content

Commit

Permalink
[Feature/#324] 문답 화면 새로운 디자인 적용 (#325)
Browse files Browse the repository at this point in the history
* feat: pingPong API path 수정

* feat: BottleStorageListResponseDTO 수정

* feat: 보틀보관함 바뀐 디자인 시스템 적용

* feat: 모래사장 보틀 확인 로직 변경

* feat: 보틀 없는 상태에서 섬 클릭 시 BottleArrival 웹뷰 띄우기
  • Loading branch information
leemhyungyu authored Oct 16, 2024
1 parent ba3706a commit 41e2298
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension BottleAPI: BaseTargetType {
case .fetchBottles:
return "api/v1/bottles"
case .fetchBottleStorageList:
return "api/v1/bottles/ping-pong"
return "api/v2/bottles/ping-pong"
case let .fetchBottlePingPong(bottleID):
return "api/v1/bottles/ping-pong/\(bottleID)"
case let .readBottle(bottleID):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import Foundation
// MARK: - Bottle Storage List

public struct BottleStorageListResponseDTO: Decodable {
let activeBottles: [BottleStorageItemResponseDTO]?
let doneBottles: [BottleStorageItemResponseDTO]?
let pingPongBottles: [BottleStorageItemResponseDTO]?

public struct BottleStorageItemResponseDTO: Decodable {
let age: Int?
Expand All @@ -21,6 +20,8 @@ public struct BottleStorageListResponseDTO: Decodable {
let mbti: String?
let userImageUrl: String?
let userName: String?
let lastActivatedAt: String?
let lastStatus: String?

public func toDomain() -> BottleStorageItem {
return BottleStorageItem(
Expand All @@ -30,15 +31,15 @@ public struct BottleStorageListResponseDTO: Decodable {
keyword: keyword ?? [],
mbti: mbti ?? "",
userImageUrl: userImageUrl ?? "",
userName: userName ?? ""
userName: userName ?? "",
lastActivatedAt: lastActivatedAt ?? "",
lastStatus: PingPongLastStatus(rawValue: lastStatus ?? "")
)
}
}

public func toDomain() -> BottleStorageList {
return BottleStorageList(
activeBottles: activeBottles?.map { $0.toDomain() } ?? [],
doneBottles: doneBottles?.map { $0.toDomain() } ?? []
)
pingPongBottles: pingPongBottles?.map { $0.toDomain() } ?? [])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@
//

public struct BottleStorageList: Decodable {
public let activeBottles: [BottleStorageItem]
public let doneBottles: [BottleStorageItem]
public let pingPongBottles: [BottleStorageItem]

public init(
activeBottles: [BottleStorageItem],
doneBottles: [BottleStorageItem]
pingPongBottles: [BottleStorageItem]
) {
self.activeBottles = activeBottles
self.doneBottles = doneBottles
self.pingPongBottles = pingPongBottles
}

}

public struct BottleStorageItem: Decodable, Equatable {
Expand All @@ -27,6 +23,8 @@ public struct BottleStorageItem: Decodable, Equatable {
public let mbti: String
public let userImageUrl: String
public let userName: String?
public let lastActivatedAt: String?
public let lastStatus: PingPongLastStatus?

public init(
age: Int?,
Expand All @@ -35,7 +33,9 @@ public struct BottleStorageItem: Decodable, Equatable {
keyword: [String],
mbti: String,
userImageUrl: String,
userName: String?
userName: String?,
lastActivatedAt: String?,
lastStatus: PingPongLastStatus?
) {
self.age = age
self.id = id
Expand All @@ -44,5 +44,26 @@ public struct BottleStorageItem: Decodable, Equatable {
self.mbti = mbti
self.userImageUrl = userImageUrl
self.userName = userName
self.lastActivatedAt = lastActivatedAt
self.lastStatus = lastStatus
}
}

public enum PingPongLastStatus: String, Decodable {
/// 대화는 시작했으나 두 사람 모두 문답을 작성하지 않았을 때
case noAnswerFromBoth = "NO_ANSWER_FROM_BOTH"
/// 상대방이 새로운 문답을 작성했을 때
case answerFromOther = "ANSWER_FROM_OTHER"
/// 상대방이 사진을 공유했을 때
case photoSharedByOther = "PHOTO_SHARED_BY_OTHER"
/// 상대방이 연락처를 공유했을 때
case contactSharedByOther = "CONTACT_SHARED_BY_OTHER"
/// 내가 문답을 작성했을 때 (상대방은 작성X)
case answerFromMeOnly = "ANSWER_FROM_ME_ONLY"
/// 내가 사진을 공유했을 때 (상대방은 공유X)
case photoSharedByMeOnly = "PHOTO_SHARED_BY_ME_ONLY"
/// 내가 연락처를 공유했을 때 (상대방은 공유X)
case contactSharedByMeOnly = "CONTACT_SHARED_BY_ME_ONLY"
/// 대화가 중단됐을 때
case conversationStopped = "CONVERSATION_STOPPED"
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import CoreLoggerInterface
import DomainBottle
import FeatureReportInterface
import FeatureBottleArrivalInterface

import ComposableArchitecture

Expand All @@ -18,11 +19,12 @@ extension BottleStorageFeature {
let reducer = Reduce<State, Action> { state, action in
switch action {
case .onAppear:
state.isLoading = true
return popToRootAndReload(state: &state)

case let .bottleStorageListFetched(bottleStorageList):
state.activeBottleList = bottleStorageList.activeBottles
state.doneBottlsList = bottleStorageList.doneBottles
state.pingPongBottleList = bottleStorageList.pingPongBottles
state.isLoading = false
return .none

case let .bottleStorageItemDidTapped(bottleID, isRead, userName):
Expand All @@ -33,9 +35,8 @@ extension BottleStorageFeature {
)))
return .none

case let .bottleActiveStateTabButtonTapped(activeState):
state.selectedActiveStateTab = activeState
return .none
case .sandBeachButtonDidTapped:
return .send(.delegate(.sandBeachButtonDidTapped))

case let .path(.element(id: _, action: .pingPongDetail(.delegate(delegate)))):

Expand Down Expand Up @@ -74,7 +75,6 @@ extension BottleStorageFeature {
self.init(reducer: reducer)

func popToRootAndReload(state: inout State) -> Effect<Action> {
state.selectedActiveStateTab = .active
state.path.removeAll()
return .run { send in
let bottleStorageList = try await bottleClient.fetchBottleStorageList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,30 @@ public struct BottleStorageFeature {

@ObservableState
public struct State: Equatable {
// 보틀 상태 선택 탭(대화 중, 완료)
let bottleActiveStateTabs: [BottleActiveState]
var selectedActiveStateTab: BottleActiveState
var currentSelectedBottles: [BottleStorageItem] {
switch selectedActiveStateTab {
case .active:
return activeBottleList ?? []
case .done:
return doneBottlsList ?? []
}
}

// 보틀 리스트
var activeBottleList: [BottleStorageItem]?
var doneBottlsList: [BottleStorageItem]?
var pingPongBottleList: [BottleStorageItem]

var path = StackState<Path.State>()
var isLoading: Bool = false

public init() {
self.bottleActiveStateTabs = BottleActiveState.allCases
self.selectedActiveStateTab = .active
self.pingPongBottleList = []
}
}

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

// 보틀 상태 선택 탭(대화 중, 완료)
case bottleActiveStateTabButtonTapped(BottleActiveState)

// 보틀 리스트
case bottleStorageListFetched(BottleStorageList)
case bottleStorageItemDidTapped(
bottleID: Int,
isRead: Bool,
userName: String
)
case sandBeachButtonDidTapped
case selectedTabDidChanged(selectedTab: TabType)
case delegate(Delegate)
// ETC.
Expand All @@ -68,6 +54,7 @@ public struct BottleStorageFeature {

public enum Delegate {
case selectedTabDidChanged(selectedTab: TabType)
case sandBeachButtonDidTapped
}
}

Expand All @@ -78,16 +65,25 @@ public struct BottleStorageFeature {
}
}

public enum BottleActiveState: String, CaseIterable, Equatable {
case active
case done

extension PingPongLastStatus {
var title: String {
switch self {
case .active:
return "대화 중"
case .done:
return "완료"
case .noAnswerFromBoth:
return "문답을 시작해 주세요"
case .answerFromOther:
return "새로운 문답이 도착했어요"
case .photoSharedByOther:
return "사진이 도착했어요"
case .contactSharedByOther:
return "연락처가 도착했어요"
case .answerFromMeOnly:
return "문답을 보냈어요"
case .photoSharedByMeOnly:
return "사진을 공유했어요"
case .contactSharedByMeOnly:
return "연락처를 공유했어요"
case .conversationStopped:
return "대화가 중단됐어요"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,37 @@ import SwiftUI
import SharedDesignSystem
import FeatureReportInterface
import FeatureTabBarInterface
import FeatureBottleArrivalInterface

import ComposableArchitecture

public struct BottleStorageView: View {
@Perception.Bindable private var store: StoreOf<BottleStorageFeature>

public init(store: StoreOf<BottleStorageFeature>) {
self.store = store
self.store = store
}

public var body: some View {
WithPerceptionTracking {
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
VStack(spacing: 0.0) {
bottleActiveStateSelectTab

bottlsList
.padding(.horizontal, .md)
.padding(.top, 32.0)
}
.padding(.top, 72)
.frame(maxHeight: .infinity, alignment: .top)
.background(to: ColorToken.background(.primary))
.padding(.bottom, BottleConstants.bottomTabBarHeight.value)
.setTabBar(selectedTab: .bottleStorage) { selectedTab in
store.send(.selectedTabDidChanged(selectedTab: selectedTab))
}
.overlay {
if store.pingPongBottleList.isEmpty && store.isLoading {
LoadingIndicator()
}
}
} destination: { store in
WithPerceptionTracking {
switch store.state {
Expand Down Expand Up @@ -64,62 +69,48 @@ public struct BottleStorageView: View {
// MARK: - Private Views

private extension BottleStorageView {
var bottleActiveStateSelectTab: some View {
HStack(spacing: .xs) {
OutlinedStyleButton(
.small(contentType: .text),
title: BottleActiveState.active.title,
buttonType: .throttle,
isSelected: store.selectedActiveStateTab == BottleActiveState.active,
action: {
store.send(.bottleActiveStateTabButtonTapped(.active))
}
)

OutlinedStyleButton(
.small(contentType: .text),
title: BottleActiveState.done.title,
buttonType: .throttle,
isSelected: store.selectedActiveStateTab == BottleActiveState.done,
action: {
store.send(.bottleActiveStateTabButtonTapped(.done))
}
)

Spacer()
}
.padding(.md)
}

@ViewBuilder
var bottlsList: some View {
if store.currentSelectedBottles.isEmpty && store.activeBottleList != nil {
VStack(spacing: .xxl) {
HStack(spacing: 0.0) {
if store.pingPongBottleList.isEmpty && !store.isLoading {
VStack(alignment: .center, spacing: 0.0) {

Spacer()
BottleImageView(type: .local(bottleImageSystem: .illustraition(.basket)))
.frame(height: 180)
.frame(width: 180)
.aspectRatio(1.0, contentMode: .fit)
.padding(.bottom, .xl)

WantedSansStyleText(
"아직 보관 중인\n보틀이 없어요!",
style: .title1,
"아직 대화를 시작하지 않으셨군요!",
style: .subTitle1,
color: .primary
)

Spacer()
}
.padding(.bottom, .xs)

WantedSansStyleText(
"마음에 드는 상대를 찾아\n가치관 문답을 시작해 볼까요?",
style: .body,
color: .tertiary
)
.lineSpacing(5)
.multilineTextAlignment(.center)
.padding(.bottom, .xl)

SolidButton(title: "모래사장 바로가기", sizeType: .extraSmall, buttonType: .throttle, action: { store.send(.sandBeachButtonDidTapped) })

GeometryReader { geometry in
BottleImageView(type: .local(bottleImageSystem: .illustraition(.basket)))
.frame(height: geometry.size.width)
Spacer()
}
.aspectRatio(1.0, contentMode: .fit)
}
} else {
ScrollView {
VStack(spacing: .md) {
ForEach(store.currentSelectedBottles, id: \.id) { bottle in
BottleStorageItem(
ForEach(store.pingPongBottleList, id: \.id) { bottle in
PingPongUserView(
status: bottle.lastStatus?.title ?? "",
lastPingPongTime: bottle.lastActivatedAt ?? "",
userName: bottle.userName ?? "(없음)",
age: bottle.age ?? 0,
mbti: bottle.mbti,
keywords: bottle.keyword,
imageURL: bottle.userImageUrl,
isRead: bottle.isRead
)
Expand Down
Loading

0 comments on commit 41e2298

Please sign in to comment.