diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 10343a7..804b3f5 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-15 steps: - name: Checkout code diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/Contents.json new file mode 100644 index 0000000..900f766 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "badge_background_graphic.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "badge_background_graphic@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "badge_background_graphic@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic.png b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic.png new file mode 100644 index 0000000..4be8c76 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@2x.png new file mode 100644 index 0000000..b56c7f9 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@3x.png new file mode 100644 index 0000000..ba75df6 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/Contents.json new file mode 100644 index 0000000..5192400 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "emotion_multiple_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "emotion_multiple_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "emotion_multiple_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge.png new file mode 100644 index 0000000..3f50126 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@2x.png new file mode 100644 index 0000000..7017eab Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@3x.png new file mode 100644 index 0000000..417517b Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/Contents.json new file mode 100644 index 0000000..ba86c7f --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "emotion_single_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "emotion_single_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "emotion_single_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge.png new file mode 100644 index 0000000..3a58867 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@2x.png new file mode 100644 index 0000000..f104638 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@3x.png new file mode 100644 index 0000000..a694557 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/Contents.json new file mode 100644 index 0000000..cf9879d --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "none_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "none_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "none_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge.png new file mode 100644 index 0000000..6fb4f9e Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@2x.png new file mode 100644 index 0000000..b36f35e Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@3x.png new file mode 100644 index 0000000..91dcc26 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/Contents.json new file mode 100644 index 0000000..27e957b --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "report_multiple_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "report_multiple_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "report_multiple_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge.png new file mode 100644 index 0000000..f3a3773 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@2x.png new file mode 100644 index 0000000..cc78532 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@3x.png new file mode 100644 index 0000000..e2f6d93 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/Contents.json new file mode 100644 index 0000000..c16efdd --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "report_single_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "report_single_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "report_single_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge.png new file mode 100644 index 0000000..a31f66b Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@2x.png new file mode 100644 index 0000000..13f198b Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@3x.png new file mode 100644 index 0000000..fc78b2c Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/Contents.json new file mode 100644 index 0000000..8999496 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "routine_multiple_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "routine_multiple_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "routine_multiple_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge.png new file mode 100644 index 0000000..1280194 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@2x.png new file mode 100644 index 0000000..b108ad1 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@3x.png new file mode 100644 index 0000000..eb66b3b Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/Contents.json new file mode 100644 index 0000000..017dbca --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "routine_single_badge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "routine_single_badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "routine_single_badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge.png b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge.png new file mode 100644 index 0000000..bc10ee5 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@2x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@2x.png new file mode 100644 index 0000000..0ccd58b Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@3x.png b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@3x.png new file mode 100644 index 0000000..d3531ae Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@3x.png differ diff --git a/Projects/Presentation/Sources/ActivityHistory/Model/ActivityBadge.swift b/Projects/Presentation/Sources/ActivityHistory/Model/ActivityBadge.swift new file mode 100644 index 0000000..6e250a3 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/Model/ActivityBadge.swift @@ -0,0 +1,47 @@ +// +// ActivityBadge.swift +// Presentation +// +// Created by 최정인 on 7/7/26. +// + +import UIKit + +enum ActivityBadge { + case emotionBadge + case routineBadge + case reportBadge + + var badgeTitle: String { + switch self { + case .emotionBadge: "점점 감정에\n솔직해지고 계시네요!" + case .routineBadge: "큰 성취를\n이루셨군요, 대단해요!" + case .reportBadge: "덕분에 도시가\n개선되고 있어요!" + } + } + + var expertTitle: String? { + switch self { + case .emotionBadge: "의욕 전문가" + case .routineBadge: "체크 전문가" + case .reportBadge: "외출 전문가" + } + } + + // TODO: 추후 서버에서 이미지 url 값을 받는다면, 수정될 가능성 있음 + var singleImage: UIImage? { + switch self { + case .emotionBadge: BitnagilGraphic.emotionSingleBadgeGraphic + case .routineBadge: BitnagilGraphic.routineSingleBadgeGraphic + case .reportBadge: BitnagilGraphic.reportSingleBadgeGraphic + } + } + + var multipleImage: UIImage? { + switch self { + case .emotionBadge: BitnagilGraphic.emotionMultipleBadgeGraphic + case .routineBadge: BitnagilGraphic.routineMultipleBadgeGraphic + case .reportBadge: BitnagilGraphic.reportMultipleBadgeGraphic + } + } +} diff --git a/Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift b/Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift new file mode 100644 index 0000000..791ce7b --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift @@ -0,0 +1,202 @@ +// +// ActivityHistoryViewController.swift +// Presentation +// +// Created by 최정인 on 7/1/26. +// + +import Combine +import Domain +import FSCalendar +import SnapKit +import UIKit + +final class ActivityHistoryViewController: BaseViewController { + private enum Layout { + static let horizontalMargin: CGFloat = 21 + static let navigationBarHeight: CGFloat = 41 + static let badgeSectionBackgroundImageViewHeight: CGFloat = 358 + static let youthOpportunityStackViewSpacing: CGFloat = 12 + static let youthOpportunityViewHeight: CGFloat = 64 + static let youthOpportunityIconSize: CGFloat = 24 + static let youthOpportunityStackViewHorizontalMargin: CGFloat = 20 + static let emotionHistoryLabelTopSpacing: CGFloat = 32 + static let emotionHistoryLabelHeight: CGFloat = 48 + static let emotionCalendarVerticalSpacing: CGFloat = 32 + static let emotionCalendarHeight: CGFloat = 395 + } + + private let scrollView = UIScrollView() + private let contentView = UIView() + + private let badgeSectionBackgroundImageView = UIImageView() + private let badgeSectionView = ActivityBadgeSectionView() + + private let youthOpportunityView = UIView() + private let youthOpportunityStackView = UIStackView() + private let youthOpportunityIcon = UIImageView() + private let youthOpportunityLabel = UILabel() + private let youthOpportunityButton = UIButton() + + private let emotionCalendarSectionView = UIView() + private let emotionHistoryLabel = UILabel() + private let emotionCalendarView = EmotionCalendarView() + private var cancellables: Set + + override init(viewModel: ActivityHistoryViewModel) { + cancellables = [] + super.init(viewModel: viewModel) + viewModel.action(input: .fetchMonthlyBadge(date: .now)) + viewModel.action(input: .fetchMonthlyEmotionHistory(date: .now)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewSafeAreaInsetsDidChange() { + super.viewSafeAreaInsetsDidChange() + let bottomInset = view.safeAreaInsets.bottom + scrollView.contentInset.bottom = bottomInset + scrollView.verticalScrollIndicatorInsets.bottom = bottomInset + } + + override func configureAttribute() { + super.configureAttribute() + view.backgroundColor = .white + + scrollView.showsVerticalScrollIndicator = false + scrollView.bounces = false + scrollView.contentInsetAdjustmentBehavior = .never + + badgeSectionBackgroundImageView.image = BitnagilGraphic.badgeBackgroundGraphic + badgeSectionBackgroundImageView.isUserInteractionEnabled = false + + youthOpportunityView.backgroundColor = BitnagilColor.orange25 + + youthOpportunityStackView.axis = .horizontal + youthOpportunityStackView.alignment = .center + youthOpportunityStackView.spacing = Layout.youthOpportunityStackViewSpacing + + youthOpportunityIcon.image = BitnagilIcon.reportIcon + youthOpportunityIcon.contentMode = .scaleAspectFit + + youthOpportunityLabel.text = "청년 공고도 확인할 수 있어요!" + youthOpportunityLabel.font = BitnagilFont(style: .body2, weight: .semiBold).font + youthOpportunityLabel.textColor = BitnagilColor.gray10 + + youthOpportunityButton.setTitle("더보기", for: .normal) + youthOpportunityButton.titleLabel?.font = BitnagilFont(style: .body2, weight: .semiBold).font + youthOpportunityButton.setTitleColor(BitnagilColor.orange500, for: .normal) + + emotionCalendarSectionView.backgroundColor = .white + emotionHistoryLabel.text = "감정 구슬 기록" + emotionHistoryLabel.font = BitnagilFont(style: .title3, weight: .semiBold).font + emotionHistoryLabel.textColor = BitnagilColor.gray30 + } + + override func configureLayout() { + super.configureLayout() + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(scrollView) + scrollView.addSubview(contentView) + + badgeSectionBackgroundImageView.addSubview(badgeSectionView) + [emotionHistoryLabel, emotionCalendarView].forEach { + emotionCalendarSectionView.addSubview($0) + } + + [badgeSectionBackgroundImageView, youthOpportunityView, emotionCalendarSectionView].forEach { + contentView.addSubview($0) + } + + badgeSectionBackgroundImageView.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + make.height.equalTo(Layout.badgeSectionBackgroundImageViewHeight) + } + + badgeSectionView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview().offset(Layout.navigationBarHeight / 2) + } + + youthOpportunityView.addSubview(youthOpportunityStackView) + let spacerView = UIView() + [youthOpportunityIcon, youthOpportunityLabel, spacerView, youthOpportunityButton].forEach { + youthOpportunityStackView.addArrangedSubview($0) + } + + scrollView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + contentView.snp.makeConstraints { make in + make.edges.equalTo(scrollView.contentLayoutGuide) + make.width.equalTo(scrollView.frameLayoutGuide) + } + + youthOpportunityView.snp.makeConstraints { make in + make.top.equalTo(badgeSectionBackgroundImageView.snp.bottom) + make.horizontalEdges.equalToSuperview() + make.height.equalTo(Layout.youthOpportunityViewHeight) + } + + youthOpportunityStackView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Layout.youthOpportunityStackViewHorizontalMargin) + make.centerY.equalToSuperview() + } + + youthOpportunityIcon.snp.makeConstraints { make in + make.size.equalTo(Layout.youthOpportunityIconSize) + } + + emotionCalendarSectionView.snp.makeConstraints { make in + make.top.equalTo(youthOpportunityView.snp.bottom) + make.horizontalEdges.equalTo(safeArea) + make.bottom.equalToSuperview() + } + + emotionHistoryLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.emotionHistoryLabelTopSpacing) + make.height.equalTo(Layout.emotionHistoryLabelHeight) + make.leading.equalToSuperview().inset(Layout.horizontalMargin) + } + + emotionCalendarView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.emotionCalendarVerticalSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalMargin) + make.height.equalTo(Layout.emotionCalendarHeight) + make.bottom.equalToSuperview().inset(Layout.emotionCalendarVerticalSpacing) + } + } + + override func bind() { + emotionCalendarView.dateSelected.sink { [weak self] date, emotion in + guard let emotion else { return } + self?.presentEmotionDetail(date: date, emotion: emotion) + } + .store(in: &cancellables) + + viewModel.output.monthlyBadgePublisher.sink { [weak self] badges in + guard let self else { return } + self.badgeSectionView.configureBadge(badges: badges) + } + .store(in: &cancellables) + + viewModel.output.monthlyEmotionHistoryPublisher.sink { [weak self] emotionRecords in + guard let self else { return } + self.emotionCalendarView.update(records: emotionRecords) + } + .store(in: &cancellables) + } + + private func presentEmotionDetail(date: Date, emotion: Marble) { + let emotionDetailViewController = EmotionDetailViewController(date: date, emotion: emotion) + if let sheet = emotionDetailViewController.sheetPresentationController { + sheet.detents = [.medium()] + sheet.prefersGrabberVisible = true + } + present(emotionDetailViewController, animated: true) + } +} diff --git a/Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift b/Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift new file mode 100644 index 0000000..2cb2e33 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift @@ -0,0 +1,75 @@ +// +// ActivityBadgeExpertComponentView.swift +// Presentation +// +// Created by 최정인 on 7/1/26. +// + +import SnapKit +import UIKit + +final class ActivityBadgeExpertComponentView: UIView { + private enum Layout { + static let cornerRadius: CGFloat = 8 + static let stackViewSpacing: CGFloat = 6 + static let verticalInset: CGFloat = 8 + static let horizontalInset: CGFloat = 13 + static let iconSize: CGFloat = 15 + } + + private let stackView = UIStackView() + private let iconView = UIImageView() + private let expertLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: .zero) + configureAttribute() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureAttribute() { + backgroundColor = BitnagilColor.orange700 + layer.masksToBounds = true + layer.cornerRadius = Layout.cornerRadius + + stackView.axis = .horizontal + stackView.spacing = Layout.stackViewSpacing + + iconView.image = BitnagilIcon.shineIcon + + expertLabel.font = BitnagilFont(style: .caption1, weight: .medium).font + expertLabel.textColor = .white + } + + private func configureLayout() { + addSubview(stackView) + stackView.addArrangedSubview(iconView) + stackView.addArrangedSubview(expertLabel) + + stackView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.edges.equalToSuperview().inset( + UIEdgeInsets( + top: Layout.verticalInset, + left: Layout.horizontalInset, + bottom: Layout.verticalInset, + right: Layout.horizontalInset)) + } + + iconView.snp.makeConstraints { make in + make.size.equalTo(Layout.iconSize) + } + } + + func configure(expertTitle: String) { + expertLabel.text = expertTitle + if expertTitle == "예비 전문가" { + iconView.image = BitnagilIcon.shineIcon?.withRenderingMode(.alwaysTemplate) + iconView.tintColor = .white + } + } +} diff --git a/Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeSectionView.swift b/Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeSectionView.swift new file mode 100644 index 0000000..0580dc0 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeSectionView.swift @@ -0,0 +1,107 @@ +// +// ActivityBadgeSectionView.swift +// Presentation +// +// Created by 최정인 on 7/7/26. +// + +import SnapKit +import UIKit + +final class ActivityBadgeSectionView: UIView { + private enum Layout { + static let badgeStackViewSpacing: CGFloat = 12 + static let badgeImageSingleSize: CGFloat = 125 + static let badgeImagemultipleSize: CGFloat = 100 + static let badgeExpertViewHeight: CGFloat = 34 + static let badgeExpertViewWidth: CGFloat = 101 + } + + private let badgeStackView = UIStackView() + private let badgeTitleLabel = UILabel() + private let badgeImageStackView = UIStackView() + private let badgeExpertView = ActivityBadgeExpertComponentView() + + override init(frame: CGRect) { + super.init(frame: .zero) + configureAttribute() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureAttribute() { + badgeStackView.axis = .vertical + badgeStackView.alignment = .center + badgeStackView.spacing = Layout.badgeStackViewSpacing + + badgeTitleLabel.attributedText = BitnagilFont( + family: .cafe24Ssurround, + style: .cafe24Title1, + weight: .light).attributedString(text: " ") + badgeTitleLabel.textAlignment = .center + badgeTitleLabel.numberOfLines = 2 + badgeTitleLabel.textColor = .white + + badgeImageStackView.axis = .horizontal + badgeImageStackView.alignment = .center + badgeImageStackView.distribution = .equalSpacing + badgeImageStackView.spacing = 0 + } + + private func configureLayout() { + addSubview(badgeStackView) + [badgeTitleLabel, badgeImageStackView, badgeExpertView].forEach { + badgeStackView.addArrangedSubview($0) + } + + badgeStackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + badgeExpertView.snp.makeConstraints { make in + make.height.equalTo(Layout.badgeExpertViewHeight) + make.width.greaterThanOrEqualTo(Layout.badgeExpertViewWidth) + } + } + + func configureBadge(badges: [ActivityBadge]) { + var badgeImages: [UIImage?] = [] + switch badges.count { + case 0: + badgeTitleLabel.text = "오늘, 작은 변화를\n만들어볼까요?" + badgeExpertView.configure(expertTitle: "예비 전문가") + badgeImages.append(BitnagilGraphic.noneBadgeGraphic) + case 2: + badgeTitleLabel.text = "하나만 더 모으면\n이번 달이 완성돼요!" + badgeExpertView.configure(expertTitle: "능숙한 전문가") + badgeImages = badges.map({ $0.multipleImage }) + case 3: + badgeTitleLabel.text = "꾸준함이 이번 달을\n가득 채웠어요!" + badgeExpertView.configure(expertTitle: "완벽한 전문가") + badgeImages = badges.map({ $0.multipleImage }) + default: + badgeTitleLabel.text = badges.first?.badgeTitle + badgeExpertView.configure(expertTitle: badges.first?.expertTitle ?? "") + badgeImages.append(badges.first?.singleImage) + } + + badgeImageStackView.arrangedSubviews.forEach { + badgeImageStackView.removeArrangedSubview($0) + $0.removeFromSuperview() + } + + let badgeImageSize: CGFloat = badges.count > 1 ? Layout.badgeImagemultipleSize : Layout.badgeImageSingleSize + badgeImages.forEach { badgeImage in + let imageView = UIImageView() + imageView.image = badgeImage + imageView.contentMode = .scaleAspectFit + imageView.snp.makeConstraints { make in + make.size.equalTo(badgeImageSize) + } + badgeImageStackView.addArrangedSubview(imageView) + } + } +} diff --git a/Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarCell.swift b/Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarCell.swift new file mode 100644 index 0000000..bd4d604 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarCell.swift @@ -0,0 +1,57 @@ +// +// EmotionCalendarCell.swift +// Presentation +// +// Created by 최정인 on 7/7/26. +// + +import FSCalendar +import UIKit + +final class EmotionCalendarCell: FSCalendarCell { + private let circleView = UIView() + private let circleSize: CGFloat = 34 + private var isRecorded: Bool = false + + override init!(frame: CGRect) { + super.init(frame: frame) + configureAttribute() + } + + required init!(coder aDecoder: NSCoder!) { + fatalError("init(coder:) has not been implemented") + } + + private func configureAttribute() { + circleView.isUserInteractionEnabled = false + contentView.insertSubview(circleView, at: 0) + } + + override func layoutSubviews() { + super.layoutSubviews() + + let center = titleLabel.center + circleView.frame = CGRect( + x: center.x - circleSize / 2, + y: center.y - circleSize / 2, + width: circleSize, + height: circleSize) + circleView.layer.cornerRadius = circleSize / 2 + } + + override func configureAppearance() { + super.configureAppearance() + titleLabel.font = isRecorded ? BitnagilFont(style: .subtitle1, weight: .semiBold).font : BitnagilFont(style: .subtitle1, weight: .regular).font + } + + func configure(backgroundColor: UIColor?, isRecorded: Bool = true) { + circleView.backgroundColor = backgroundColor + self.isRecorded = isRecorded + } + + override func prepareForReuse() { + super.prepareForReuse() + circleView.backgroundColor = .clear + isRecorded = false + } +} diff --git a/Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift b/Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift new file mode 100644 index 0000000..81c3af2 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift @@ -0,0 +1,205 @@ +// +// EmotionCalendarView.swift +// Presentation +// +// Created by 최정인 on 7/3/26. +// + +import Combine +import FSCalendar +import Shared +import SnapKit +import UIKit + +final class EmotionCalendarView: UIView { + private enum Layout { + static let calendarHeaderHeight: CGFloat = 0 + static let calendarWeekdayHeight: CGFloat = 46 + static let headerStackViewSpacing: CGFloat = 0 + static let headerStackViewHeight: CGFloat = 48 + static let moveMonthButtonImageSize: CGFloat = 18 + static let moveMonthButtonSize: CGFloat = 48 + static let calendarTopSpacing: CGFloat = 24 + } + + private let headerStackView = UIStackView() + private let previousMonthButtonImage = UIImageView() + private let previousMonthButton = UIButton() + private let monthLabel = UILabel() + private let nextMonthButtonImage = UIImageView() + private let nextMonthButton = UIButton() + private let calendar = FSCalendar() + + let dateSelected = PassthroughSubject<(date: Date, emotion: Marble?), Never>() + let pageChanged = PassthroughSubject() + private var emotionRecords: [String: Marble] = [:] + private let cellIdentifier = "EmotionCalendarCell" + + override init(frame: CGRect) { + super.init(frame: .zero) + configureAttribute() + configureLayout() + updateMonthLabel(date: calendar.currentPage) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureAttribute() { + calendar.register(EmotionCalendarCell.self, forCellReuseIdentifier: cellIdentifier) + calendar.dataSource = self + calendar.delegate = self + + calendar.appearance.todayColor = .clear + calendar.appearance.titleTodayColor = nil + calendar.appearance.selectionColor = .clear + + calendar.headerHeight = Layout.calendarHeaderHeight + calendar.appearance.weekdayFont = BitnagilFont(style: .body2, weight: .semiBold).font + calendar.appearance.weekdayTextColor = BitnagilColor.gray50 + + calendar.placeholderType = .fillHeadTail + calendar.scope = .month + calendar.weekdayHeight = Layout.calendarWeekdayHeight + + headerStackView.axis = .horizontal + headerStackView.spacing = Layout.headerStackViewSpacing + headerStackView.alignment = .center + + previousMonthButtonImage.image = UIImage(systemName: "chevron.left") + previousMonthButtonImage.tintColor = BitnagilColor.gray10 + previousMonthButtonImage.contentMode = .scaleAspectFit + previousMonthButton.addTarget(self, action: #selector(moveToPreviousMonth), for: .touchUpInside) + + nextMonthButtonImage.image = UIImage(systemName: "chevron.right") + nextMonthButtonImage.tintColor = BitnagilColor.gray10 + nextMonthButtonImage.contentMode = .scaleAspectFit + nextMonthButton.addTarget(self, action: #selector(moveToNextMonth), for: .touchUpInside) + + monthLabel.font = BitnagilFont(style: .subtitle1, weight: .semiBold).font + monthLabel.textColor = BitnagilColor.gray30 + monthLabel.textAlignment = .center + } + + private func configureLayout() { + addSubview(headerStackView) + addSubview(calendar) + + previousMonthButton.addSubview(previousMonthButtonImage) + nextMonthButton.addSubview(nextMonthButtonImage) + [previousMonthButton, monthLabel, nextMonthButton].forEach { + headerStackView.addArrangedSubview($0) + } + + headerStackView.snp.makeConstraints { make in + make.top.trailing.equalToSuperview() + make.height.equalTo(Layout.headerStackViewHeight) + } + + previousMonthButtonImage.snp.makeConstraints { make in + make.center.equalToSuperview() + make.size.equalTo(Layout.moveMonthButtonImageSize) + } + + previousMonthButton.snp.makeConstraints { make in + make.size.equalTo(Layout.moveMonthButtonSize) + } + + nextMonthButtonImage.snp.makeConstraints { make in + make.center.equalToSuperview() + make.size.equalTo(Layout.moveMonthButtonImageSize) + } + + nextMonthButton.snp.makeConstraints { make in + make.size.equalTo(Layout.moveMonthButtonSize) + } + + calendar.snp.makeConstraints { make in + make.top.equalTo(headerStackView.snp.bottom).offset(Layout.calendarTopSpacing) + make.horizontalEdges.bottom.equalToSuperview() + } + } + + func update(records: [String: Marble]) { + self.emotionRecords = records + calendar.reloadData() + } + + @objc private func moveToPreviousMonth() { + guard let newMonth = Calendar.current.date(byAdding: .month, value: -1, to: calendar.currentPage) + else { return } + + calendar.setCurrentPage(newMonth, animated: true) + } + + @objc private func moveToNextMonth() { + guard let newMonth = Calendar.current.date(byAdding: .month, value: 1, to: calendar.currentPage) + else { return } + + calendar.setCurrentPage(newMonth, animated: true) + } + + private func updateMonthLabel(date: Date) { + monthLabel.text = date.convertToString(dateType: .yearMonthShort) + } + + private func isInCurrentMonth(date: Date) -> Bool { + return Calendar.current.isDate(date, equalTo: calendar.currentPage, toGranularity: .month) + } +} + +// MARK: - FSCalendarDataSource, FSCalendarDelegate, FSCalendarDelegateAppearance +extension EmotionCalendarView: FSCalendarDataSource, FSCalendarDelegate, FSCalendarDelegateAppearance { + func calendar( + _ calendar: FSCalendar, + appearance: FSCalendarAppearance, + titleDefaultColorFor date: Date + ) -> UIColor? { + guard isInCurrentMonth(date: date) + else { return BitnagilColor.gray80 } + + let key = date.convertToString(dateType: .yearMonthDate) + guard let emotion = emotionRecords[key] else { return BitnagilColor.gray30 } + return emotion.textColor + } + + func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell { + guard let cell = calendar.dequeueReusableCell( + withIdentifier: cellIdentifier, + for: date, + at: position) as? EmotionCalendarCell + else { return FSCalendarCell() } + + let key = date.convertToString(dateType: .yearMonthDate) + guard let emotion = emotionRecords[key] else { return cell } + + if isInCurrentMonth(date: date) { + cell.configure(backgroundColor: emotion.backgroundColor) + } else { + cell.configure(backgroundColor: BitnagilColor.gray98, isRecorded: false) + } + return cell + } + + func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) { + let key = date.convertToString(dateType: .yearMonthDate) + let emotion = emotionRecords[key] + + guard let emotion else { + calendar.deselect(date) + return + } + + if !isInCurrentMonth(date: date) { + calendar.setCurrentPage(date, animated: true) + } + dateSelected.send((date: date, emotion: emotion)) + calendar.deselect(date) + } + + func calendarCurrentPageDidChange(_ calendar: FSCalendar) { + updateMonthLabel(date: calendar.currentPage) + pageChanged.send(calendar.currentPage) + } +} diff --git a/Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift b/Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift new file mode 100644 index 0000000..90c5378 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift @@ -0,0 +1,33 @@ +// +// EmotionDetailViewController.swift +// Presentation +// +// Created by 최정인 on 7/7/26. +// + +import SnapKit +import UIKit + +final class EmotionDetailViewController: UIViewController { + private let date: Date + private let emotion: Marble + private let label = UILabel() + + init(date: Date, emotion: Marble) { + self.date = date + self.emotion = emotion + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + print("\(emotion.koreanDescription)") + view.backgroundColor = .white + } + + +} diff --git a/Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift b/Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift new file mode 100644 index 0000000..f76d181 --- /dev/null +++ b/Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift @@ -0,0 +1,54 @@ +// +// ActivityHistoryViewModel.swift +// Presentation +// +// Created by 최정인 on 7/1/26. +// + +import Combine +import Foundation + +final class ActivityHistoryViewModel: ViewModel { + enum Input { + case fetchMonthlyBadge(date: Date) + case fetchMonthlyEmotionHistory(date: Date) + } + + struct Output { + let monthlyBadgePublisher: AnyPublisher<[ActivityBadge], Never> + let monthlyEmotionHistoryPublisher: AnyPublisher<[String: Marble], Never> + } + + private(set) var output: Output + private let monthlyBadgeSubject = CurrentValueSubject<[ActivityBadge], Never>([]) + private let monthlyEmotionHistorySubject = CurrentValueSubject<[String: Marble], Never>([:]) + + init() { + self.output = Output( + monthlyBadgePublisher: monthlyBadgeSubject.eraseToAnyPublisher(), + monthlyEmotionHistoryPublisher: monthlyEmotionHistorySubject.eraseToAnyPublisher()) + } + + func action(input: Input) { + switch input { + case .fetchMonthlyBadge(let date): + // TODO: 추후 서버 로직 붙이기 + Task { + let badges: [ActivityBadge] = [] + monthlyBadgeSubject.send(badges) + } + case .fetchMonthlyEmotionHistory(let date): + // TODO: 추후 서버 로직 붙이기 + Task { + let mockEmotionRecords: [String: Marble] = [ + "2026-07-01": .ANXIETY, + "2026-07-05": .FATIGUE, + "2026-07-11": .LETHARGY, + "2026-07-16": .VITALITY, + "2026-07-23": .CALM + ] + monthlyEmotionHistorySubject.send(mockEmotionRecords) + } + } + } +} diff --git a/Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift b/Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift index eaece15..b3e6bdf 100644 --- a/Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift +++ b/Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift @@ -62,4 +62,15 @@ enum BitnagilGraphic { // MARK: - Report static let loadingFomoGraphic = UIImage(named: "loading_fomo_graphic", in: bundle, with: nil) static let successFomoGraphic = UIImage(named: "success_fomo_graphic", in: bundle, with: nil) + + // MARK: - Badge + static let badgeBackgroundGraphic = UIImage(named: "badge_background_graphic", in: bundle, with: nil) + // TODO: - 뱃지 이미지는 추후 서버로부터 값을 받아온다면 삭제될 가능성 있음 + static let noneBadgeGraphic = UIImage(named: "none_badge", in: bundle, with: nil) + static let emotionSingleBadgeGraphic = UIImage(named: "emotion_single_badge", in: bundle, with: nil) + static let emotionMultipleBadgeGraphic = UIImage(named: "emotion_multiple_badge", in: bundle, with: nil) + static let routineSingleBadgeGraphic = UIImage(named: "routine_single_badge", in: bundle, with: nil) + static let routineMultipleBadgeGraphic = UIImage(named: "routine_multiple_badge", in: bundle, with: nil) + static let reportSingleBadgeGraphic = UIImage(named: "report_single_badge", in: bundle, with: nil) + static let reportMultipleBadgeGraphic = UIImage(named: "report_multiple_badge", in: bundle, with: nil) } diff --git a/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift b/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift index fcd20a2..ac50a85 100644 --- a/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift +++ b/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift @@ -141,5 +141,9 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol { return ReportDetailViewModel(reportRepository: reportRepository) } + + DIContainer.shared.register(type: ActivityHistoryViewModel.self) { _ in + return ActivityHistoryViewModel() + } } } diff --git a/Projects/Presentation/Sources/Common/View/TabBarView.swift b/Projects/Presentation/Sources/Common/View/TabBarView.swift index 5957fc8..77c58d2 100644 --- a/Projects/Presentation/Sources/Common/View/TabBarView.swift +++ b/Projects/Presentation/Sources/Common/View/TabBarView.swift @@ -45,11 +45,15 @@ final public class TabBarView: UITabBarController { guard let recommendedRoutineViewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else { fatalError("recommendedRoutineViewModel 의존성이 등록되지 않았습니다.") } + guard let activityHistoryViewModel = DIContainer.shared.resolve(type: ActivityHistoryViewModel.self) + else { fatalError("activityHistoryViewModel 의존성이 등록되지 않았습니다.") } + guard let mypageViewModel = DIContainer.shared.resolve(type: MypageViewModel.self) else { fatalError("mypageViewModel 의존성이 등록되지 않았습니다.") } let homeView = HomeViewController(viewModel: homeViewModel) let recommendView = RecommendedRoutineViewController(viewModel: recommendedRoutineViewModel) + let activityHistoryView = ActivityHistoryViewController(viewModel: activityHistoryViewModel) let mypageView = MypageView(viewModel: mypageViewModel) homeView.tabBarItem = UITabBarItem( @@ -62,6 +66,11 @@ final public class TabBarView: UITabBarController { image: BitnagilIcon.recommendIcon, selectedImage: BitnagilIcon.recommendIcon) + activityHistoryView.tabBarItem = UITabBarItem( + title: "활동 일지", + image: BitnagilIcon.reportFillIcon, + selectedImage: BitnagilIcon.reportFillIcon) + mypageView.tabBarItem = UITabBarItem( title: "마이페이지", image: BitnagilIcon.mypageIcon, @@ -70,6 +79,7 @@ final public class TabBarView: UITabBarController { viewControllers = [ UINavigationController(rootViewController: homeView), UINavigationController(rootViewController: recommendView), + UINavigationController(rootViewController: activityHistoryView), UINavigationController(rootViewController: mypageView) ] } diff --git a/Projects/Shared/Sources/Extension/Date+.swift b/Projects/Shared/Sources/Extension/Date+.swift index 2f1b80e..304494e 100644 --- a/Projects/Shared/Sources/Extension/Date+.swift +++ b/Projects/Shared/Sources/Extension/Date+.swift @@ -35,6 +35,7 @@ extension Date { case yearMonthDateWeek case yearMonthDateWeek2 case yearMonth + case yearMonthShort case dayOfWeek case date case time @@ -49,6 +50,7 @@ extension Date { case .yearMonthDateWeek: "yy.MM.dd E" case .yearMonthDateWeek2: "yyyy.MM.dd (E)" case .yearMonth: "yyyy년 M월" + case .yearMonthShort: "yy년 M월" case .dayOfWeek: "E" case .date: "d" case .time: "HH:mm:ss"