Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xCF",
"red" : "0xA9"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xB3",
"green" : "0xCD",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ enum BitnagilColor {

static let kakao = UIColor(named: "Kakao", in: bundle, compatibleWith: nil)

// MARK: - Gradient
static let gradientLeft = UIColor(named: "GradientLeft", in: bundle, compatibleWith: nil)
static let gradientRight = UIColor(named: "GradientRight", in: bundle, compatibleWith: nil)

// MARK: - Emotion Colors
static let happy = UIColor(named: "EmotionHappy", in: bundle, compatibleWith: nil)
static let lethargy = UIColor(named: "EmotionLethargy", in: bundle, compatibleWith: nil)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// NSAttributedString+.swift
// Presentation
//
// Created by 최정인 on 7/11/25.
//

import Foundation

extension NSAttributedString {
static func highlighted(text: String, highlightText: String) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: text)

attributedString.addAttribute(
.font,
value: BitnagilFont(style: .body2, weight: .regular).font,
range: NSRange(location: 0, length: text.count)
)

if let range = text.range(of: highlightText) {
let nsRange = NSRange(range, in: text)
attributedString.addAttribute(
.font,
value: BitnagilFont(style: .body2, weight: .semiBold).font,
range: nsRange
)
}
return attributedString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,25 @@ extension UIViewController {
switch navigationStyle {
case .hidden:
navigationController?.setNavigationBarHidden(true, animated: false)

case .withBackButton(let title):
navigationController?.setNavigationBarHidden(false, animated: false)
self.title = title
configureDefaultBackButton()

case .withPrograssBar(let step, let stepCount):
navigationController?.setNavigationBarHidden(false, animated: false)
configureDefaultBackButton()
configureProgressNavigationBar(step: step, stepCount: stepCount)

case .withPrograssBarWithCustomBackButton(let step, let stepCount):
navigationController?.setNavigationBarHidden(false, animated: false)
configureCustomBackButton()
configureProgressNavigationBar(step: step, stepCount: stepCount)
}
}

func configureDefaultBackButton() {
private func configureDefaultBackButton() {
let backButton = UIBarButtonItem(
image: UIImage(systemName: "chevron.left"),
style: .plain,
Expand All @@ -30,12 +41,43 @@ extension UIViewController {
navigationItem.leftBarButtonItem = backButton
}

private func configureCustomBackButton() {
let backButton = UIBarButtonItem(
image: UIImage(systemName: "chevron.left"),
style: .plain,
target: self,
action: #selector(popTwoViewControllers))
backButton.tintColor = .black
navigationItem.leftBarButtonItem = backButton
}

private func configureProgressNavigationBar(step: Int, stepCount: Int) {
self.title = ""
let progressView = ProgressBarView(step: step, stepCount: stepCount)
navigationItem.titleView = progressView
}

@objc private func popViewController() {
navigationController?.popViewController(animated: true)
}

@objc private func popTwoViewControllers() {
guard let navigationController = navigationController else { return }
let viewControllers = navigationController.viewControllers

guard viewControllers.count >= 3 else {
navigationController.popViewController(animated: true)
return
}

let targetViewController = viewControllers[viewControllers.count - 3]
navigationController.popToViewController(targetViewController, animated: true)
}
}

enum NavigationBarStyle {
case hidden
case withBackButton(title: String)
case withPrograssBar(step: Int, stepCount: Int)
case withPrograssBarWithCustomBackButton(step: Int, stepCount: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
return HomeViewModel()
}

DIContainer.shared.register(type: OnboardingViewModel.self) { _ in
return OnboardingViewModel()
}

DIContainer.shared.register(type: LoginViewModel.self) { container in
guard let loginUseCase = container.resolve(type: LoginUseCaseProtocol.self)
else { return }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,15 @@ public final class TermsAgreementView: BaseViewController<LoginViewModel> {

viewModel.output.agreementResultPublisher
.receive(on: DispatchQueue.main)
.sink { agreementResult in
.sink { [weak self] agreementResult in
guard let self else { return }
if agreementResult {
// TODO: 약관 동의 성공 시 온보딩 화면으로 이동해야 합니다.
BitnagilLogger.log(logType: .debug, message: "약관 동의 성공")
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
}
let onboardingView = OnboardingView(viewModel: onboardingViewModel, onboarding: .time)
self.navigationController?.pushViewController(onboardingView, animated: true)
} else {
// TODO: 약관 동의 실패 시, 에러 처리를 해야 합니다.
BitnagilLogger.log(logType: .error, message: "약관 동의 실패")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// OnboardingChoiceProtocol.swift
// Presentation
//
// Created by 최정인 on 7/11/25.
//

protocol OnboardingChoiceProtocol {
var mainTitle: String { get }
var subTitle: String? { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//
// OnboardingChoiceType.swift
// Presentation
//
// Created by 최정인 on 7/9/25.
//

enum OnboardingChoiceType: CaseIterable, OnboardingChoiceProtocol {
case morningTime
case eveningTime
case allTime

case never
case rarely
case sometimes
case often

case stability
case connection
case growth
case vitality

case once
case twoToThree
case fourOrMore
case notSure

var onboardingType: OnboardingType {
switch self {
case .morningTime: .time
case .eveningTime: .time
case .allTime: .time

case .never: .frequency
case .rarely: .frequency
case .sometimes: .frequency
case .often: .frequency

case .stability: .feeling
case .connection: .feeling
case .growth: .feeling
case .vitality: .feeling

case .once: .outdoorGoal
case .twoToThree: .outdoorGoal
case .fourOrMore: .outdoorGoal
case .notSure: .outdoorGoal
}
}

var mainTitle: String {
switch self {
case .morningTime: "아침을 잘 시작하고 싶어요."
case .eveningTime: "저녁을 편안하게 마무리하고 싶어요."
case .allTime: "언제든 상관 없어요."

case .never: "밖에 나가지 않고 집에서만 지냈어요."
case .rarely: "잠깐 외출했어요."
case .sometimes: "가끔 나가요."
case .often: "자주 외출해요."

case .stability: "안정감"
case .connection: "연결감"
case .growth: "성장감"
case .vitality: "생동감"

case .once: "시작이 더 중요해요."
case .twoToThree: "너무 무리하지 않아도 괜찮아요."
case .fourOrMore: "이 정도면 충분히 활력 있는 한 주가 될거에요."
case .notSure: "목표 선택을 도와드릴게요!"
}
}

var subTitle: String? {
switch self {
case .stability:
return "하루를 편안하게 보내고 싶어요."
case .connection:
return "누군가와 함께 있다는 느낌이 필요해요."
case .growth:
return "작은 변화라도 시작하고 싶어요."
case .vitality:
return "무기력을 이겨내고 활력을 찾고싶어요."

case .once:
return "일주일에 1회"
case .twoToThree:
return "일주일에 2~3회"
case .fourOrMore:
return "일주일에 4회 이상"
case .notSure:
return "아직 잘 모르겠어요"

default:
return nil
}
}

var resultTitle: String? {
switch self {
case .morningTime:
return "아침루틴"
case .eveningTime:
return "저녁루틴"
case .allTime:
return "전체루틴"

case .stability:
return "안정감"
case .connection:
return "연결감"
case .growth:
return "성장감"
case .vitality:
return "생동감"

case .once:
return "주 1회 외출"
case .twoToThree:
return "주 3회 외출"
case .fourOrMore:
return "주 4회 이상 외출"
case .notSure:
return "최소한의 외출"

default:
return nil
}
}
}
Loading