Skip to content

Commit 76fbf18

Browse files
committed
Feat: 추천 루틴 화면에 FloatingButton 추가 및 등록 화면 이동 (#T3-121)
- RecommendedRoutineView에 FloatingButton, FloatingMenu, dimmedView 추가 - 루틴 등록 화면으로 이동해야 하므로 RoutineCreationViewModel 의존성 등록
1 parent a261e70 commit 76fbf18

3 files changed

Lines changed: 87 additions & 4 deletions

File tree

Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ enum BitnagilIcon {
2323
static let ellipsisIcon = UIImage(named: "ellipsis_icon", in: bundle, with: nil)
2424
static let informationIcon = UIImage(named: "information_icon", in: bundle, with: nil)
2525
static let sortIcon = UIImage(named: "sort_icon", in: bundle, with: nil)
26+
static let addRoutineIcon = UIImage(named: "add_routine_icon", in: bundle, with: nil)
2627

2728
// MARK: - Tab Bar Icons
2829
static let homeFillIcon = UIImage(named: "home_fill_icon", in: bundle, with: nil)

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
1717

1818
public func assemble() {
1919
preAssembler.assemble()
20-
20+
2121
DIContainer.shared.register(type: HomeViewModel.self) { _ in
2222
return HomeViewModel()
2323
}
@@ -53,5 +53,9 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
5353
DIContainer.shared.register(type: EmotionRegisterViewModel.self) { _ in
5454
return EmotionRegisterViewModel()
5555
}
56+
57+
DIContainer.shared.register(type: RoutineCreationViewModel.self) { _ in
58+
return RoutineCreationViewModel()
59+
}
5660
}
5761
}

Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,30 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
2727
static let recommendedRoutineStackViewBottomSpacing: CGFloat = 50
2828
static let routineCardHeight: CGFloat = 80
2929
static let registerEmotionButtonHeight: CGFloat = 52
30+
static let floatingButtonBottomSpacing: CGFloat = 19
31+
static let floatingButtonSize: CGFloat = 52
32+
static let floatingMenuBottomSpacing: CGFloat = 15
33+
static let floatingMenuHeight: CGFloat = 64
34+
static let floatingMenuWidth: CGFloat = 144
3035
}
3136

3237
private let categoryView = RoutineCategoryView()
3338
private let headerStackView = UIStackView()
3439
private let routineLabel = UILabel()
3540
private let levelButton = RoutineLevelButton()
3641
private let levelView = SelectableItemTableView<RoutineLevelType>(items: RoutineLevelType.allCases.sorted(by: { $0.id < $1.id }))
42+
3743
private let recommendedRoutineScrollView = UIScrollView()
3844
private let recommendedRoutineStackView = UIStackView()
3945
private var recommendedRoutineCards: [Int: RecommendedRoutineCardView] = [:]
4046
private let registerEmotionButton = RegisterEmotionButton()
41-
private var cancellables: Set<AnyCancellable>
4247

48+
private var isShowingFloatinMenu: Bool = false
49+
private let dimmedView = UIView()
50+
private let floatingButton = FloatingButton()
51+
private let floatingMenu = FloatingMenuView()
52+
53+
private var cancellables: Set<AnyCancellable>
4354
public override init(viewModel: RecommendedRoutineViewModel) {
4455
cancellables = []
4556
super.init(viewModel: viewModel)
@@ -71,7 +82,7 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
7182
levelView.delegate = self
7283

7384
recommendedRoutineScrollView.showsVerticalScrollIndicator = false
74-
85+
7586
recommendedRoutineStackView.axis = .vertical
7687
recommendedRoutineStackView.spacing = Layout.recommendedRoutineStackViewSpacing
7788

@@ -83,6 +94,20 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
8394
emotionRegisterView.hidesBottomBarWhenPushed = true
8495
self.navigationController?.pushViewController(emotionRegisterView, animated: true)
8596
}, for: .touchUpInside)
97+
98+
floatingButton.addAction(UIAction { [weak self] _ in
99+
self?.toggleFloatingButton()
100+
}, for: .touchUpInside)
101+
102+
floatingMenu.isHidden = true
103+
floatingMenu.delegate = self
104+
105+
dimmedView.isHidden = true
106+
dimmedView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
107+
dimmedView.alpha = 0
108+
109+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedDimmedView))
110+
dimmedView.addGestureRecognizer(tapGesture)
86111
}
87112

88113
public override func configureLayout() {
@@ -97,6 +122,10 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
97122
view.addSubview(recommendedRoutineScrollView)
98123
recommendedRoutineScrollView.addSubview(recommendedRoutineStackView)
99124

125+
view.addSubview(dimmedView)
126+
view.addSubview(floatingMenu)
127+
view.addSubview(floatingButton)
128+
100129
categoryView.snp.makeConstraints { make in
101130
make.leading.equalTo(safeArea)
102131
make.trailing.equalTo(safeArea)
@@ -131,6 +160,23 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
131160
make.bottom.equalToSuperview().inset(Layout.recommendedRoutineStackViewBottomSpacing)
132161
make.width.equalTo(recommendedRoutineScrollView.snp.width)
133162
}
163+
164+
floatingButton.snp.makeConstraints { make in
165+
make.trailing.equalTo(safeArea).inset(Layout.horizontalMargin)
166+
make.bottom.equalTo(safeArea).inset(Layout.floatingButtonBottomSpacing)
167+
make.size.equalTo(Layout.floatingButtonSize)
168+
}
169+
170+
floatingMenu.snp.makeConstraints { make in
171+
make.trailing.equalTo(safeArea).inset(Layout.horizontalMargin)
172+
make.bottom.equalTo(floatingButton.snp.top).offset(-Layout.floatingMenuBottomSpacing)
173+
make.height.equalTo(Layout.floatingMenuHeight)
174+
make.width.equalTo(Layout.floatingMenuWidth)
175+
}
176+
177+
dimmedView.snp.makeConstraints { make in
178+
make.edges.equalToSuperview()
179+
}
134180
}
135181

136182
public override func bind() {
@@ -159,7 +205,7 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
159205
if recommendedRoutines.first?.routineCategory == .recommendation {
160206
showEmotionButton()
161207
}
162-
208+
163209
for routine in recommendedRoutines {
164210
let routineCard = RecommendedRoutineCardView(recommendedRoutine: routine)
165211
recommendedRoutineCards[routine.id] = routineCard
@@ -172,6 +218,9 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
172218
}
173219

174220
private func showBottomSheet() {
221+
if isShowingFloatinMenu {
222+
toggleFloatingButton()
223+
}
175224
presentCustomBottomSheet(contentViewController: levelView, maxHeight: Layout.bottomSheetHeight)
176225
}
177226

@@ -183,6 +232,23 @@ final class RecommendedRoutineView: BaseViewController<RecommendedRoutineViewMod
183232
make.height.equalTo(Layout.registerEmotionButtonHeight)
184233
}
185234
}
235+
236+
private func toggleFloatingButton() {
237+
floatingButton.toggle()
238+
isShowingFloatinMenu.toggle()
239+
240+
floatingMenu.isHidden = !isShowingFloatinMenu
241+
dimmedView.isHidden = !isShowingFloatinMenu
242+
243+
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) {
244+
self.dimmedView.alpha = self.isShowingFloatinMenu ? 1 : 0
245+
self.floatingMenu.alpha = self.isShowingFloatinMenu ? 1 : 0
246+
}
247+
}
248+
249+
@objc private func tappedDimmedView() {
250+
toggleFloatingButton()
251+
}
186252
}
187253

188254
// MARK: RoutineCategoryViewDelegate
@@ -208,3 +274,15 @@ extension RecommendedRoutineView: SelectableItemTableViewDelegate {
208274
levelButton.updateButton(level: didSelectLevel)
209275
}
210276
}
277+
278+
// MARK: FloatingMenuViewDelegate
279+
extension RecommendedRoutineView: FloatingMenuViewDelegate {
280+
func floatingMenuDidTapRegisterRoutineButton(_ sender: FloatingMenuView) {
281+
toggleFloatingButton()
282+
guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else {
283+
fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.")
284+
}
285+
let routineCreationView = RoutineCreationView(viewModel: routineCreationViewModel)
286+
self.navigationController?.pushViewController(routineCreationView, animated: true)
287+
}
288+
}

0 commit comments

Comments
 (0)