Skip to content
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
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,23 @@
{
"images" : [
{
"filename" : "tutorial_edit_routine_graphic.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tutorial_edit_routine_graphic@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "tutorial_edit_routine_graphic@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "tutorial_emotion_graphic.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tutorial_emotion_graphic@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "tutorial_emotion_graphic@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "tutorial_home_more_graphic.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tutorial_home_more_graphic@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "tutorial_home_more_graphic@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "tutorial_recommendation_routine_graphic.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tutorial_recommendation_routine_graphic@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "tutorial_recommendation_routine_graphic@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ enum BitnagilGraphic {
static let logoGraphic = UIImage(named: "bitnagil_logo", in: bundle, with: nil)
static let profileGraphic = UIImage(named: "profile_graphic", in: bundle, with: nil)
static let grayLogoGraphic = UIImage(named: "bitnagil_logo_gray", in: bundle, with: nil)

// MARK: - Tutorial
static let tutorialEmotionGraphic = UIImage(named: "tutorial_emotion_graphic", in: bundle, with: nil)
static let tutorialHomeMoreGraphic = UIImage(named: "tutorial_home_more_graphic", in: bundle, with: nil)
static let tutorialRecommendationRoutineGraphic = UIImage(named: "tutorial_recommendation_routine_graphic", in: bundle, with: nil)
static let tutorialEditRoutineGraphic = UIImage(named: "tutorial_edit_routine_graphic", in: bundle, with: nil)
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ final class HomeViewController: BaseViewController<HomeViewModel> {
helpButton.setImage(BitnagilIcon.helpIcon, for: .normal)
alarmButton.setImage(BitnagilIcon.alarmIcon, for: .normal)

helpButton.addAction(
UIAction { [weak self] _ in
let tutorialView = TutorialViewController()
tutorialView.hidesBottomBarWhenPushed = true
self?.navigationController?.pushViewController(tutorialView, animated: true)
},
for: .touchUpInside)

let homeLabelText = "\(nickname)\n"
homeLabel.attributedText = BitnagilFont(
family: .cafe24Ssurround,
Expand Down
54 changes: 54 additions & 0 deletions Projects/Presentation/Sources/Tutorial/Model/Tutorial.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Tutorial.swift
// Presentation
//
// Created by 최정인 on 9/3/25.
//

import UIKit

enum Tutorial: CaseIterable {
case emotionRegister
case homeMore
case recommendationRoutine
case editRoutine

var title: String {
switch self {
case .emotionRegister:
"오늘 감정 등록하기란?"
case .homeMore:
"홈에서 더보기란?"
case .recommendationRoutine:
"맞춤 추천 루틴이란?"
case .editRoutine:
"루틴 수정하기란?"
}
}

var description: String {
switch self {
case .emotionRegister:
"오늘 느끼는 감정을 선택하면, 그 기분에 맞춰 추천 루틴을\n확인할 수 있어요."
case .homeMore:
"루틴 리스트에서 세부 항목(세부 루틴, 반복, 기간, 시간)을\n확인하고, 필요 시 수정하거나 삭제할 수 있어요."
case .recommendationRoutine:
"처음 설정한 목표에 맞춰 루틴을 추천받을 수 있으며,\n필요할 때 언제든 수정할 수 있어요."
case .editRoutine:
"등록한 루틴은 수정할 수 있으며, 변경 내용을 당일부터 또는\n다음 날부터 적용하도록 선택할 수 있어요."
}
}

var tutorialImage: UIImage? {
switch self {
case .emotionRegister:
BitnagilGraphic.tutorialEmotionGraphic
case .homeMore:
BitnagilGraphic.tutorialHomeMoreGraphic
case .recommendationRoutine:
BitnagilGraphic.tutorialRecommendationRoutineGraphic
case .editRoutine:
BitnagilGraphic.tutorialEditRoutineGraphic
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// TutorialTableCell.swift
// Presentation
//
// Created by 최정인 on 9/3/25.
//

import SnapKit
import UIKit

final class TutorialTableCell: UITableViewCell {
private enum Layout {
static let titleLableLeadingSpacing: CGFloat = 20
static let chevronIconSize: CGFloat = 24
static let chevronIconTrailingSpacing: CGFloat = 11
}

private let titleLabel = UILabel()
private let chevronIcon = UIImageView()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureAttribute()
configureLayout()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(title: String) {
titleLabel.text = title
}

private func configureAttribute() {
layer.masksToBounds = true
layer.cornerRadius = 12
backgroundColor = BitnagilColor.gray99

titleLabel.font = BitnagilFont(style: .body2, weight: .medium).font
titleLabel.textColor = BitnagilColor.gray10

chevronIcon.image = BitnagilIcon.chevronRightIcon
chevronIcon.contentMode = .scaleAspectFit
}

private func configureLayout() {
contentView.addSubview(titleLabel)
contentView.addSubview(chevronIcon)

titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(Layout.titleLableLeadingSpacing)
make.centerY.equalToSuperview()
}

chevronIcon.snp.makeConstraints { make in
make.size.equalTo(Layout.chevronIconSize)
make.centerY.equalToSuperview()
make.trailing.equalToSuperview().inset(Layout.chevronIconTrailingSpacing)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// TutorialDetailViewController.swift
// Presentation
//
// Created by 최정인 on 9/3/25.
//

import SnapKit
import UIKit

final class TutorialDetailViewController: UIViewController {
private enum Layout {
static let horizontalMargin: CGFloat = 24
static let titleLabelTopSpacing: CGFloat = 26
static let titleLabelHeight: CGFloat = 24
static let descriptionLabelTopSpacing: CGFloat = 10
static let descriptionLabelHeight: CGFloat = 40
static let tutorialImageTopSpacing: CGFloat = 24
}

private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let tutorialImage = UIImageView()
var onDismiss: (() -> Void)?
private let tutorial: Tutorial

init(tutorial: Tutorial) {
self.tutorial = tutorial
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
configureAttribute()
configureLayout()
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
onDismiss?()
}

private func configureAttribute() {
view.backgroundColor = BitnagilColor.gray99

titleLabel.text = tutorial.title
titleLabel.font = BitnagilFont(style: .title3, weight: .semiBold).font
titleLabel.textColor = BitnagilColor.gray10

let descriptionText = tutorial.description
descriptionLabel.attributedText = BitnagilFont(style: .body2, weight: .medium).attributedString(text: descriptionText)
descriptionLabel.textColor = BitnagilColor.gray40
descriptionLabel.numberOfLines = 2

tutorialImage.image = tutorial.tutorialImage
tutorialImage.contentMode = .scaleAspectFit
}

private func configureLayout() {
let safeArea = view.safeAreaLayoutGuide

view.addSubview(titleLabel)
view.addSubview(descriptionLabel)
view.addSubview(tutorialImage)

titleLabel.snp.makeConstraints { make in
make.top.equalTo(safeArea).offset(Layout.titleLabelTopSpacing)
make.leading.equalTo(safeArea).offset(Layout.horizontalMargin)
make.height.equalTo(Layout.titleLabelHeight)
}

descriptionLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(Layout.descriptionLabelTopSpacing)
make.horizontalEdges.equalTo(safeArea).inset(Layout.horizontalMargin)
make.height.equalTo(Layout.descriptionLabelHeight)
}

tutorialImage.snp.makeConstraints { make in
make.top.equalTo(descriptionLabel.snp.bottom).offset(Layout.tutorialImageTopSpacing)
make.horizontalEdges.equalTo(safeArea).inset(Layout.horizontalMargin)
if let image = tutorialImage.image {
let aspectRatio = image.size.height / image.size.width
make.height.equalTo(tutorialImage.snp.width).multipliedBy(aspectRatio)
}
}
}
}
Loading