Skip to content

Commit

Permalink
Merge pull request #33 from Cheffi-GitHub/feat/HomeView_Connect_API
Browse files Browse the repository at this point in the history
�홈 뷰 API 연결
  • Loading branch information
ericKwon95 authored Nov 26, 2024
2 parents f3e27b6 + fdd812c commit deb9425
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 134 deletions.
16 changes: 10 additions & 6 deletions Cheffi.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
54F217B22C2D685400892DBD /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 54F217B12C2D685400892DBD /* Kingfisher */; };
602CE7522CCD946800A6E245 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 602CE7512CCD946100A6E245 /* UIApplicationExtension.swift */; };
6040AAF52CA275FA0016338E /* LineHeightModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6040AAF42CA275F30016338E /* LineHeightModifier.swift */; };
A60FB02B2CBD065A00B0B4A1 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A60FB02A2CBD065A00B0B4A1 /* Colors.xcassets */; };
607EC5622CBA965E00DA1557 /* AddRestaurantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EC5612CBA965E00DA1557 /* AddRestaurantView.swift */; };
607EC5642CBA97CA00DA1557 /* AddRestaurantFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EC5632CBA97C500DA1557 /* AddRestaurantFeature.swift */; };
608223792CBAA9230062B0CF /* TermsFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A681E99F2C4FDF8400B796AA /* TermsFeature.swift */; };
Expand All @@ -70,7 +69,10 @@
60BAE8BC2CBCBD34003EF52C /* SelectRegionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60BAE8BB2CBCBD34003EF52C /* SelectRegionView.swift */; };
60BAE8BE2CBCBD3E003EF52C /* SearchRestaurantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60BAE8BD2CBCBD3E003EF52C /* SearchRestaurantsView.swift */; };
60BAE8C02CBCBD45003EF52C /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60BAE8BF2CBCBD45003EF52C /* NotificationView.swift */; };
60CFBC232CE1D35B002CC503 /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CFBC222CE1D354002CC503 /* CollectionExtension.swift */; };
60E725AD2CE212BE005F5C0F /* RecommendedFollower.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E725AC2CE212BE005F5C0F /* RecommendedFollower.swift */; };
60F55F5B2CBBDDA20074008D /* HomeFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F55F5A2CBBDDA00074008D /* HomeFeature.swift */; };
A60FB02B2CBD065A00B0B4A1 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A60FB02A2CBD065A00B0B4A1 /* Colors.xcassets */; };
A611D9D12C187D6500ABF38C /* EndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A611D9D02C187D6500ABF38C /* EndPoint.swift */; };
A611D9D32C187D7800ABF38C /* RestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A611D9D22C187D7800ABF38C /* RestRouter.swift */; };
A611D9D52C187D8400ABF38C /* NetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A611D9D42C187D8400ABF38C /* NetworkClient.swift */; };
Expand Down Expand Up @@ -186,7 +188,6 @@
54F217AC2C2D2FDB00892DBD /* Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = "<group>"; };
602CE7512CCD946100A6E245 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = "<group>"; };
6040AAF42CA275F30016338E /* LineHeightModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineHeightModifier.swift; sourceTree = "<group>"; };
A60FB02A2CBD065A00B0B4A1 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
607EC5612CBA965E00DA1557 /* AddRestaurantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRestaurantView.swift; sourceTree = "<group>"; };
607EC5632CBA97C500DA1557 /* AddRestaurantFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRestaurantFeature.swift; sourceTree = "<group>"; };
60A6D9042CCC1A65002443BF /* HomeCheffiPlaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCheffiPlaceView.swift; sourceTree = "<group>"; };
Expand All @@ -195,7 +196,10 @@
60BAE8BB2CBCBD34003EF52C /* SelectRegionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectRegionView.swift; sourceTree = "<group>"; };
60BAE8BD2CBCBD3E003EF52C /* SearchRestaurantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRestaurantsView.swift; sourceTree = "<group>"; };
60BAE8BF2CBCBD45003EF52C /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = "<group>"; };
60CFBC222CE1D354002CC503 /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
60E725AC2CE212BE005F5C0F /* RecommendedFollower.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedFollower.swift; sourceTree = "<group>"; };
60F55F5A2CBBDDA00074008D /* HomeFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFeature.swift; sourceTree = "<group>"; };
A60FB02A2CBD065A00B0B4A1 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
A611D9D02C187D6500ABF38C /* EndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndPoint.swift; sourceTree = "<group>"; };
A611D9D22C187D7800ABF38C /* RestRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestRouter.swift; sourceTree = "<group>"; };
A611D9D42C187D8400ABF38C /* NetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -520,6 +524,7 @@
549B1FD32C0618B7005C9432 /* Extensions */ = {
isa = PBXGroup;
children = (
60CFBC222CE1D354002CC503 /* CollectionExtension.swift */,
602CE7512CCD946100A6E245 /* UIApplicationExtension.swift */,
549B1FD42C0618D7005C9432 /* FontExtension.swift */,
549B1FDA2C061CE3005C9432 /* ColorExtension.swift */,
Expand Down Expand Up @@ -663,6 +668,7 @@
54F217AA2C2D2F1B00892DBD /* ReviewModel.swift */,
A6DC44172C5C98E200423B90 /* OtherProfileModel.swift */,
54E285F92C2FE28700AD7939 /* TagsModel.swift */,
60E725AC2CE212BE005F5C0F /* RecommendedFollower.swift */,
5419304C2C3D798A0080CA57 /* ReviewDetailModel.swift */,
A681E9A12C53DD1700B796AA /* SignupTermsModel.swift */,
A688B0AE2C29B69600E4E3A2 /* Objects */,
Expand Down Expand Up @@ -891,6 +897,7 @@
541D2A812C4FB08C00488999 /* PhotoInfo.swift in Sources */,
54F217AD2C2D2FDB00892DBD /* Photo.swift in Sources */,
5419304F2C3D7B030080CA57 /* Restaurant.swift in Sources */,
60CFBC232CE1D35B002CC503 /* CollectionExtension.swift in Sources */,
A6801E5A2C417DDA00471691 /* LoginFeature.swift in Sources */,
A6F5E6ED2C246A750053C2E8 /* KakaoLoginHandler.swift in Sources */,
54073B7D2C0C564700597973 /* HomeView.swift in Sources */,
Expand Down Expand Up @@ -970,6 +977,7 @@
54C30E312C47EDDE00DA6FF8 /* FirstAppearModifer.swift in Sources */,
A67677F32C1738EF0089A269 /* AppEnvironment.swift in Sources */,
540BE9B02C22C607009180AE /* ReviewDetailFeature.swift in Sources */,
60E725AD2CE212BE005F5C0F /* RecommendedFollower.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1206,7 +1214,6 @@
549B1FB32C0607C9005C9432 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand All @@ -1226,7 +1233,6 @@
549B1FB42C0607C9005C9432 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand All @@ -1246,7 +1252,6 @@
549B1FB62C0607C9005C9432 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A9F8XLHP2Y;
Expand All @@ -1264,7 +1269,6 @@
549B1FB72C0607C9005C9432 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A9F8XLHP2Y;
Expand Down
14 changes: 14 additions & 0 deletions Cheffi/Module/Component/Extensions/CollectionExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// CollectionExtension.swift
// Cheffi
//
// Created by 권승용 on 11/11/24.
//

extension Collection {

// 안전한 index 접근을 위한 subscript 제공
subscript (safe index: Index) -> Element? {
return index >= startIndex && index < endIndex ? self[index] : nil
}
}
2 changes: 1 addition & 1 deletion Cheffi/Module/Component/Models/Objects/PhotoInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

struct PhotoInfo: Codable, Equatable {
struct PhotoInfo: Codable, Hashable {
let url: String?
let width: Int?
let height: Int?
Expand Down
24 changes: 24 additions & 0 deletions Cheffi/Module/Component/Models/RecommendedFollower.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// RecommendedFollower.swift
// Cheffi
//
// Created by 권승용 on 11/11/24.
//

import Foundation

typealias RecommendedFollowerResponse = RestResponse<[RecommendedFollower]>

struct RecommendedFollower: Codable, Hashable, Identifiable {
let id: Int
let nickname: String
let photo: PhotoInfo
let instruction: String
let followers: Int
var isFollowed: Bool

enum CodingKeys: String, CodingKey {
case id, nickname, photo, instruction, followers
case isFollowed = "followed"
}
}
2 changes: 1 addition & 1 deletion Cheffi/Module/Component/Models/TagsModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

typealias TagsResponse = RestResponse<[TagsModel]>

struct TagsModel: Codable, Equatable, Identifiable {
struct TagsModel: Codable, Hashable, Identifiable {
let id: Int
let name: String
let type: String
Expand Down
94 changes: 61 additions & 33 deletions Cheffi/Module/Feature/Home/CheffiPlace/HomeCheffiPlaceFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Perception

@Reducer
struct HomeCheffiPlaceFeature {

@Dependency(\.networkClient) var networkClient

@ObservableState
Expand Down Expand Up @@ -47,65 +48,92 @@ struct HomeCheffiPlaceFeature {
}

enum Action {
case onFirstAppear

case tagTapped(TagsModel)
case toolTipTapped
case reviewCellTapped
case registerRestaurantButtonTapped

case requestTags
case tagsResponse(Result<TagsResponse, Error>)
case requestCheffiPlace(cursor: Int = 0, tagId: Int)
case cheffiPlaceResponse(tagId: Int, Result<ReviewResponse, Error>)
case toolTipTapped
case tagTapped
case reviewCellTapped
case registerRestaurantButtonTapped
}

// TODO: 무한 스크롤 기능 구현 필요
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .requestTags:
return Effect.publisher {
return networkClient
.request(.tags(type: "FOOD"))
.map { Action.tagsResponse(.success($0)) }
.catch { Just(Action.tagsResponse(.failure($0))) }

// MARK: - Life Cycle
case .onFirstAppear:
return .run { send in
await send(.requestTags)
}

case let .cheffiPlaceResponse(tagId, .success(response)):
state.cheffiPlaceReviews[tagId] = response.data ?? []
// MARK: - User Interaction
case .toolTipTapped:
state.showTooltip.toggle()
return .none

case let .cheffiPlaceResponse(_, .failure(error)):
print(error)
return .none
case let .tagTapped(tag):
return .run { send in
await send(.requestCheffiPlace(cursor: 0, tagId: tag.id))
}

case let .tagsResponse(.success(response)):
state.tags = response.data ?? []
case .reviewCellTapped:
print("reviewCell 탭")
return .none

case let .tagsResponse(.failure(error)):
print(error)
case .registerRestaurantButtonTapped:
return .none

// MARK: - Network
case .requestTags:
return Effect.publisher {
return networkClient
.request(.tags(type: "FOOD"))
.map { Action.tagsResponse(.success($0)) }
.catch { Just(Action.tagsResponse(.failure($0))) }
}

case let .tagsResponse(response):
switch response {
case .success(let response):
state.tags = response.data ?? []
guard !state.tags.isEmpty else {
return .none
}
let requestTag = state.tags[0]
return .run { send in
await send(.requestCheffiPlace(cursor: 0, tagId: requestTag.id))
}

case .failure(let error):
print(error)
return .none
}

case .requestCheffiPlace(let cursor, let tagId):
return Effect.publisher {
return networkClient
// TODO: 위치 서비스에서 받아온 정보를 통해 province / city 제공
.request(.cheffiPlace(province: "서울특별시", city: "강남구", cursor: cursor, size: 16, tag_id: tagId))
.map { Action.cheffiPlaceResponse(tagId: tagId, .success($0)) }
.catch { Just(Action.cheffiPlaceResponse(tagId: tagId, .failure($0))) }
}

case .toolTipTapped:
state.showTooltip.toggle()
return .none

case .tagTapped:
print("api 호출")
return .none

case .reviewCellTapped:
print("reviewCell 탭")
return .none

case .registerRestaurantButtonTapped:
return .none
case let .cheffiPlaceResponse(tagId, response):
switch response {
case .success(let response):
state.cheffiPlaceReviews[tagId] = response.data ?? []
return .none

case .failure(let error):
print(error)
return .none
}
}
}
}
Expand Down
43 changes: 35 additions & 8 deletions Cheffi/Module/Feature/Home/CheffiPlace/HomeCheffiPlaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ struct HomeCheffiPlaceView: View {
@State private var selectedTabID = 0
@State private var isAddRestaurantPresented: Bool = false

let columns = [GridItem(.flexible()), GridItem(.flexible())]
private let columns = [GridItem(.flexible()), GridItem(.flexible())]

// 스크롤뷰 높이 = 화면 크기 - (네비게이션 + 타이틀 + 카테고리 높이 + safeArea 높이)
private var cheffiPlaceScrollViewHeight: CGFloat {
UIWindow().screen.bounds.height - (156 + safeAreaInsets.top + safeAreaInsets.bottom)
}

@Environment(\.safeAreaInsets) private var safeAreaInsets

Expand All @@ -24,8 +29,17 @@ struct HomeCheffiPlaceView: View {
VStack(spacing: 0) {
header
.zIndex(1)
foodCategories
tabView
if !store.tags.isEmpty {
tags
tabView
.frame(height: cheffiPlaceScrollViewHeight)
} else {
tagEmpty
.frame(height: cheffiPlaceScrollViewHeight)
}
}
.onFirstAppear {
store.send(.onFirstAppear)
}
.fullScreenCover(isPresented: $isAddRestaurantPresented) {
AddRestaurantView()
Expand Down Expand Up @@ -58,7 +72,7 @@ struct HomeCheffiPlaceView: View {
.padding(.bottom, 24)
}

private var foodCategories: some View {
private var tags: some View {
ScrollView(.horizontal) {
HStack(spacing: 0) {
ForEach(store.tags) { tag in
Expand All @@ -79,11 +93,13 @@ struct HomeCheffiPlaceView: View {
}
.onTapGesture {
selectedTabID = tag.id
store.send(.tagTapped(tag))
}
}
}
}
.padding(.leading, 16)
.frame(height: 40)
}
.background {
VStack {
Expand Down Expand Up @@ -147,10 +163,21 @@ struct HomeCheffiPlaceView: View {
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
// 화면 크기 - 네비게이션 + 타이틀 + 카테고리 높이 + safeArea 높이
.frame(
height: UIWindow().screen.bounds.height - 156 - (safeAreaInsets.top + safeAreaInsets.bottom)
)
}

private var tagEmpty: some View {
Group {
VStack(alignment: .center, spacing: 0) {
Image(name: Home.homeEmpty)
.padding(.bottom, 12)
Text("음식 카테고리를 불러오지 못했습니다 ㅠㅠ\n 조금 기다렸다가 다시 시도해 주세요!")
.font(.suit(.medium, 14))
.lineHeight(22, fontHeight: 14)
.foregroundStyle(.g60)
.padding(.bottom, 18)
.multilineTextAlignment(.center)
}
}
}
}

Expand Down
Loading

0 comments on commit deb9425

Please sign in to comment.