diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/Contents.json b/GAMSS/Resources/Assets.xcassets/Onboarding/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/GAMSS/Resources/Assets.xcassets/Onboarding/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/Contents.json b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/Contents.json new file mode 100644 index 0000000..1741688 --- /dev/null +++ b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "thought_notes.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "thought_notes@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "thought_notes@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes.png b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes.png new file mode 100644 index 0000000..50fc2cb Binary files /dev/null and b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes.png differ diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes@2x.png b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes@2x.png new file mode 100644 index 0000000..20bf6ad Binary files /dev/null and b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes@2x.png differ diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes@3x.png b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes@3x.png new file mode 100644 index 0000000..c6e72db Binary files /dev/null and b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes.imageset/thought_notes@3x.png differ diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/Contents.json b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/Contents.json new file mode 100644 index 0000000..f2e5802 --- /dev/null +++ b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "thought_notes_discard.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "thought_notes_discard@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "thought_notes_discard@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard.png b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard.png new file mode 100644 index 0000000..f574e2e Binary files /dev/null and b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard.png differ diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard@2x.png b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard@2x.png new file mode 100644 index 0000000..e929662 Binary files /dev/null and b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard@2x.png differ diff --git a/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard@3x.png b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard@3x.png new file mode 100644 index 0000000..2b1ae3b Binary files /dev/null and b/GAMSS/Resources/Assets.xcassets/Onboarding/thought_notes_discard.imageset/thought_notes_discard@3x.png differ diff --git a/GAMSS/Sources/Presentation/Onboarding/OnboardingPage.swift b/GAMSS/Sources/Presentation/Onboarding/OnboardingPage.swift new file mode 100644 index 0000000..56b173f --- /dev/null +++ b/GAMSS/Sources/Presentation/Onboarding/OnboardingPage.swift @@ -0,0 +1,64 @@ +// +// OnboardingPage.swift +// GAMSS +// +// Created by LEEGEONJUN on 8/14/26. +// + +import SwiftUI + +enum OnboardingPage: Int, CaseIterable, Identifiable { + case talk + case emotions + case empty + + var id: Int { rawValue } + + var title: String { + switch self { + case .talk: + "누구한테 말 못할 말이 있다면\n이야기해보세요" + case .emotions: + "6가지의 감정 친구들과\n이야기를 이어가보세요" + case .empty: + "안좋았던 감정을\n비워보세요" + } + } + + var buttonTitle: String { + switch self { + case .talk, .emotions: + "다음" + case .empty: + "시작하기" + } + } + + var buttonColor: Color { + switch self { + case .talk, .emotions: + .colorGray950 + case .empty: + .colorApricot + } + } + + var showsBackButton: Bool { + self != .talk + } + + var showsSkipButton: Bool { + self != .empty + } + + var backgroundImage: Image { + switch self { + case .talk: + return Image(.thoughtNotes) + case .emotions: + return Image(.emotions) + case .empty: + return Image(.thoughtNotesDiscard) + } + } +} diff --git a/GAMSS/Sources/Presentation/Onboarding/OnboardingView.swift b/GAMSS/Sources/Presentation/Onboarding/OnboardingView.swift new file mode 100644 index 0000000..c583163 --- /dev/null +++ b/GAMSS/Sources/Presentation/Onboarding/OnboardingView.swift @@ -0,0 +1,122 @@ +// +// OnboardingView.swift +// GAMSS +// +// Created by LEEGEONJUN on 8/14/26. +// + +import SwiftUI + +struct OnboardingView: View { + var onFinish: () -> Void + + @State private var currentPage: OnboardingPage = .talk + + var body: some View { + VStack(spacing: 0) { + header + .padding(.horizontal, 18) + .padding(.vertical, Spacing.spacing400) + + Spacer() + + pageContent(currentPage) + .id(currentPage) + .padding(.horizontal, 31) + + Spacer() + + pageIndicator + .padding(.bottom, Spacing.spacing400) + + actionButton + .padding(.horizontal, 18) + .padding(.bottom, Spacing.spacing400) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.colorWhite) + .animation(.easeInOut(duration: 0.25), value: currentPage) + } + + private var header: some View { + HStack { + if currentPage.showsBackButton { + Button { + goToPreviousPage() + } label: { + Image(systemName: "chevron.left") + .foregroundStyle(Color.colorGray900) + } + } + + Spacer() + + if currentPage.showsSkipButton { + Button { + onFinish() + } label: { + Text("건너뛰기") + .typography(.body5Medium) + .foregroundStyle(Color.colorGray500) + } + } + } + .frame(height: 24) + } + + private func pageContent(_ page: OnboardingPage) -> some View { + VStack(spacing: Spacing.spacing600) { + page.backgroundImage + .resizable() + .scaledToFit() + + Text(page.title) + .typography(.subtitle2) + .foregroundStyle(Color.colorGray900) + .multilineTextAlignment(.center) + } + } + + private var pageIndicator: some View { + HStack(spacing: Spacing.spacing100) { + ForEach(OnboardingPage.allCases) { page in + Circle() + .fill(page == currentPage ? Color.colorGray700 : Color.colorGray200) + .frame(width: 8, height: 8) + } + } + } + + private var actionButton: some View { + Button { + goToNextPage() + } label: { + Text(currentPage.buttonTitle) + .typography(.subtitle3) + .foregroundStyle(Color.colorWhite) + .frame(maxWidth: .infinity) + .frame(height: 54) + } + .background(currentPage.buttonColor) + .clipShape(RoundedRectangle(cornerRadius: Radius.radius200)) + } + + private func goToNextPage() { + guard let next = OnboardingPage(rawValue: currentPage.rawValue + 1) else { + onFinish() + return + } + currentPage = next + } + + private func goToPreviousPage() { + guard let previous = OnboardingPage(rawValue: currentPage.rawValue - 1) else { + return + } + currentPage = previous + } +} + +#Preview { + OnboardingView(onFinish: {}) +} diff --git a/GAMSS/Sources/Presentation/Root/RootView.swift b/GAMSS/Sources/Presentation/Root/RootView.swift index 0187785..abdbfd7 100644 --- a/GAMSS/Sources/Presentation/Root/RootView.swift +++ b/GAMSS/Sources/Presentation/Root/RootView.swift @@ -14,6 +14,7 @@ final class LoginSession { struct RootView: View { @State private var loginSession = LoginSession() + @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false var body: some View { Group { @@ -30,7 +31,13 @@ struct RootView: View { ) ) case .autoLoginPending, .loggedIn: - MainTabView() + if hasCompletedOnboarding { + MainTabView() + } else { + OnboardingView { + hasCompletedOnboarding = true + } + } } } .environment(loginSession)