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

온보딩 화면 구현 #104

Merged
merged 8 commits into from
Mar 19, 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
12 changes: 4 additions & 8 deletions PyeonHaeng-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
objects = {

/* Begin PBXBuildFile section */
9CE4B4712B6F0B57002DC446 /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE4B4702B6F0B57002DC446 /* OnboardingPage.swift */; };
9CE4B4732B6F0BA3002DC446 /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE4B4722B6F0BA3002DC446 /* OnboardingViewModel.swift */; };
9CE4B4752B6F78E8002DC446 /* OnboardingPageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE4B4742B6F78E8002DC446 /* OnboardingPageControl.swift */; };
BA0564062B624646003D6DC7 /* Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = BA0564052B624646003D6DC7 /* Shared.xcconfig */; };
BA0564092B6248EA003D6DC7 /* HomeAPI in Frameworks */ = {isa = PBXBuildFile; productRef = BA0564082B6248EA003D6DC7 /* HomeAPI */; };
Expand Down Expand Up @@ -70,6 +68,7 @@
E57F2AA42B7717EA00E12B3D /* ProductInfoAPI in Frameworks */ = {isa = PBXBuildFile; productRef = E57F2AA32B7717EA00E12B3D /* ProductInfoAPI */; settings = {ATTRIBUTES = (Required, ); }; };
E57F2AA62B7717EA00E12B3D /* ProductInfoAPISupport in Frameworks */ = {isa = PBXBuildFile; productRef = E57F2AA52B7717EA00E12B3D /* ProductInfoAPISupport */; };
E57F2AA82B774CA700E12B3D /* ProductInfoDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57F2AA72B774CA700E12B3D /* ProductInfoDependency.swift */; };
E5DB08EB2BA7EC7000E83910 /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DB08EA2BA7EC7000E83910 /* OnboardingPage.swift */; };
E5F2EC452B64926100EE0838 /* PromotionTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F2EC442B64926100EE0838 /* PromotionTagView.swift */; };
/* End PBXBuildFile section */

Expand All @@ -84,8 +83,6 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
9CE4B4702B6F0B57002DC446 /* OnboardingPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = "<group>"; };
9CE4B4722B6F0BA3002DC446 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
9CE4B4742B6F78E8002DC446 /* OnboardingPageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageControl.swift; sourceTree = "<group>"; };
BA0564022B62179A003D6DC7 /* Core */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Core; sourceTree = "<group>"; };
BA0564032B6219D4003D6DC7 /* APIService */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = APIService; sourceTree = "<group>"; };
Expand Down Expand Up @@ -138,6 +135,7 @@
E5462C652B65677B00E9FDF2 /* PromotionTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionTag.swift; sourceTree = "<group>"; };
E55DD5112B91DE9500AA63C0 /* SearchListCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchListCardView.swift; sourceTree = "<group>"; };
E57F2AA72B774CA700E12B3D /* ProductInfoDependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductInfoDependency.swift; sourceTree = "<group>"; };
E5DB08EA2BA7EC7000E83910 /* OnboardingPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = "<group>"; };
E5F2EC3F2B637D4A00EE0838 /* ProductInfoDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductInfoDetailView.swift; sourceTree = "<group>"; };
E5F2EC442B64926100EE0838 /* PromotionTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionTagView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -257,10 +255,9 @@
BA28F1832B6155710052855E /* OnboardingScene */ = {
isa = PBXGroup;
children = (
9CE4B4702B6F0B57002DC446 /* OnboardingPage.swift */,
9CE4B4722B6F0BA3002DC446 /* OnboardingViewModel.swift */,
BA28F1842B6155810052855E /* OnboardingView.swift */,
9CE4B4742B6F78E8002DC446 /* OnboardingPageControl.swift */,
E5DB08EA2BA7EC7000E83910 /* OnboardingPage.swift */,
);
path = OnboardingScene;
sourceTree = "<group>";
Expand Down Expand Up @@ -617,12 +614,11 @@
E5462C662B65677B00E9FDF2 /* PromotionTag.swift in Sources */,
E50584532B763C8C002FDACF /* ProductInfoViewModel.swift in Sources */,
BAB720342B9325F200C2CA1A /* PromotionSelectBottomSheetView.swift in Sources */,
9CE4B4732B6F0BA3002DC446 /* OnboardingViewModel.swift in Sources */,
E5DB08EB2BA7EC7000E83910 /* OnboardingPage.swift in Sources */,
9CE4B4752B6F78E8002DC446 /* OnboardingPageControl.swift in Sources */,
BA28F18E2B6156420052855E /* SettingsView.swift in Sources */,
E57F2AA82B774CA700E12B3D /* ProductInfoDependency.swift in Sources */,
E55DD5122B91DE9500AA63C0 /* SearchListCardView.swift in Sources */,
9CE4B4712B6F0B57002DC446 /* OnboardingPage.swift in Sources */,
BA097F0E2B9CA82A002D3E1E /* MailSheetView.swift in Sources */,
E52F371B2B947DC8000EBAD5 /* SearchViewModel.swift in Sources */,
BAE159DE2B663A9A002DCF94 /* HomeProductSorterView.swift in Sources */,
Expand Down

This file was deleted.

Binary file not shown.
Binary file not shown.
26 changes: 23 additions & 3 deletions PyeonHaeng-iOS/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,6 @@
}
},
"건너뛰기" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -614,7 +613,6 @@
}
},
"다음" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -768,6 +766,29 @@
}
}
},
"세븐일레븐, CU, 이마트 24, GS 25, 미니스톱의 수많은 행사정보를 ‘편행’한곳에서 만나보세요." : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Explore the promotions from various convenience stores such as 7-Eleven, CU, Emart 24, GS 25, Mini Stop all in one place with 'Pyunhaeng'."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "セブンイレブン、CU、イマート24、GS25、ミニストップなど、様々なコンビニのプロモーション情報を「편행(ピョンハン)」で一か所にまとめて見つけてください。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "세븐일레븐, CU, 이마트 24, GS 25, 미니스톱의\\n수많은 행사정보를 ‘편행’한곳에서 만나보세요."
}
}
}
},
"수많은 혜택을 한곳에서" : {
"extractionState" : "stale",
"localizations" : {
Expand Down Expand Up @@ -950,7 +971,6 @@
}
},
"편행 시작하기" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down
6 changes: 3 additions & 3 deletions PyeonHaeng-iOS/Sources/AppRootComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import HomeAPI
import HomeAPISupport
import Network

// MARK: - HomeDependency
// MARK: - AppRootDependency

protocol HomeDependency {
protocol AppRootDependency {
var homeService: HomeServiceRepresentable { get }
}

// MARK: - AppRootComponent

struct AppRootComponent: HomeDependency {
struct AppRootComponent: AppRootDependency {
let homeService: HomeServiceRepresentable

init() {
Expand Down
10 changes: 9 additions & 1 deletion PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import SwiftUI

struct HomeView<ViewModel>: View where ViewModel: HomeViewModelRepresentable {
@StateObject private var viewModel: ViewModel
@State private var isOnboardingSheetOpen = false
@AppStorage("isFirstLaunch") private var isFirstLaunch: Bool = false

init(viewModel: @autoclosure @escaping () -> ViewModel) {
_viewModel = .init(wrappedValue: viewModel())
Expand All @@ -21,7 +23,6 @@ struct HomeView<ViewModel>: View where ViewModel: HomeViewModelRepresentable {
NavigationStack {
ZStack {
HomeProductListView<ViewModel>()

VStack(spacing: 0) {
VStack {
HomeProductDetailSelectionView<ViewModel>()
Expand Down Expand Up @@ -74,7 +75,14 @@ struct HomeView<ViewModel>: View where ViewModel: HomeViewModelRepresentable {
}
}
.tint(.accent)
.fullScreenCover(isPresented: $isOnboardingSheetOpen) {
OnboardingView()
}
.onAppear {
if !isFirstLaunch {
isOnboardingSheetOpen = true
isFirstLaunch = true
}
viewModel.trigger(.fetchProducts)
viewModel.trigger(.fetchCount)
}
Expand Down
28 changes: 22 additions & 6 deletions PyeonHaeng-iOS/Sources/Scenes/OnboardingScene/OnboardingPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@
// OnboardingPage.swift
// PyeonHaeng-iOS
//
// Created by 방현석 on 2/4/24.
// Created by 김응철 on 3/18/24.
//

import Foundation
import DesignSystem
import SwiftUI

// MARK: - OnboardingPage

struct OnboardingPage {
struct OnboardingPage: Identifiable {
let id = UUID()
let title: String
let body: String
let imageName: String
let image: Image
let tag: Int

static let pages: [OnboardingPage] = [
OnboardingPage(
title: "하나사면 하나 더",
body: "다양한 편의점의 1+1, 2+1 행사 제품 정보로\n알뜰하고 합리적으로 소비할수 있어요.",
image: .onboarding1,
tag: 0
),
OnboardingPage(
title: "수많은 혜택을 한곳에서",
body: "세븐일레븐, CU, 이마트 24, GS 25, 미니스톱의 수많은 행사정보를 ‘편행’한곳에서 만나보세요.",
image: .onboarding2,
tag: 1
),
]
}
101 changes: 53 additions & 48 deletions PyeonHaeng-iOS/Sources/Scenes/OnboardingScene/OnboardingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,81 @@
// Created by 홍승현 on 1/24/24.
//

import DesignSystem
import SwiftUI

// MARK: - OnboardingView

struct OnboardingView: View {
@StateObject private var viewModel = OnboardingViewModel()
@State private var currentPage = 0
@Environment(\.dismiss) private var dismiss
private let pages = OnboardingPage.pages

var body: some View {
NavigationStack {
VStack {
TabView(selection: $viewModel.currentPage) {
ForEach(0 ..< viewModel.pageCount, id: \.self) { index in
ZStack {
TabView(selection: $currentPage) {
ForEach(pages) { page in
VStack {
Spacer().frame(height: viewModel.spacerHeight(for: index))
Image(viewModel.currentImageName)
Spacer()
page.image
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.horizontal, 40)
Spacer()
Text(LocalizedStringKey(page.title))
.multilineTextAlignment(.center)
.font(.h2)
Spacer().frame(height: 16)
Text(LocalizedStringKey(page.body))
.font(.body2)
.foregroundStyle(Color.gray500)
.multilineTextAlignment(.center)
Spacer()
.frame(height: 120)
}
.tag(index)
.tag(page.tag)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))

// 커스텀 페이지 컨트롤
OnboardingPageControl(currentPage: $viewModel.currentPage, pageCount: viewModel.pageCount)
.padding(.vertical)

// 본문 제목
Text(viewModel.currentTitle)
.font(.h2)
.transition(.opacity.combined(with: .slide))
.animation(.easeInOut, value: viewModel.currentPage)

Spacer().frame(height: 16)

// 본문 내용
Text(viewModel.currentBody)
.font(.body2)
.foregroundStyle(Color.gray500)
.multilineTextAlignment(.center)
.padding(.top, 8)
.transition(.opacity.combined(with: .slide))
.animation(.easeInOut, value: viewModel.currentPage)

Spacer().frame(height: 84)

Button(action: {
withAnimation {
viewModel.nextPage()
VStack {
Spacer()
OnboardingPageControl(
currentPage: $currentPage,
pageCount: pages.count
)
.padding(.top, 100)
Button {
if currentPage == pages.count - 1 {
dismiss()
} else {
withAnimation {
currentPage += 1
}
}
} label: {
Text(currentPage == pages.count - 1 ? "편행 시작하기" : "다음")
.font(.h5)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.green500)
.clipShape(.rect(cornerRadius: 10))
.padding(.horizontal, 20)
}
}) {
Text(viewModel.nextButtonText)
.font(.h5)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.green500)
.clipShape(.rect(cornerRadius: 10))
.padding(.horizontal, 20)
.padding(.bottom, 8)
.padding(.top, 208)
}
.padding(.bottom, 8)
}
.tabViewStyle(.page)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(viewModel.skipButtonText) {
// 건너뛰기 버튼 액션
Button {
dismiss()
} label: {
Text("건너뛰기")
.font(.b2)
.foregroundStyle(Color.green500)
}
.foregroundStyle(Color.green500)
}
}
}
Expand Down
Loading