diff --git a/iOS/Projects/App/Sources/Application/SceneDelegate.swift b/iOS/Projects/App/Sources/Application/SceneDelegate.swift index f925bca4..105399f1 100644 --- a/iOS/Projects/App/Sources/Application/SceneDelegate.swift +++ b/iOS/Projects/App/Sources/Application/SceneDelegate.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import RecordFeature import UIKit final class SceneDelegate: UIResponder, UIWindowSceneDelegate { diff --git a/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutCardCell.swift b/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutCardCell.swift new file mode 100644 index 00000000..8196eb54 --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutCardCell.swift @@ -0,0 +1,123 @@ +// +// WorkoutCardCell.swift +// RecordFeature +// +// Created by MaraMincho on 11/16/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - WorkoutCardCell + +class WorkoutCardCell: UICollectionViewCell { + static let identifier = "WorkoutCardCell" + + override init(frame: CGRect) { + super.init(frame: frame) + makeShadowAndRounded() + backgroundColor = DesignSystemColor.primaryBackGround + setupConstraints() + } + + override var isSelected: Bool { + didSet { + if isSelected { + makeSelectUI() + } else { + makeDeslectUI() + } + } + } + + private let workoutIconDescriptionLabel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .title3) + label.textAlignment = .center + label.text = "달리기에용" + label.contentMode = .scaleAspectFit + + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let workoutIcon: UIImageView = { + let config = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 120)) + let icon = UIImage(systemName: "figure.run", withConfiguration: config) + let imageView = UIImageView(image: icon) + imageView.contentMode = .scaleAspectFit + imageView.tintColor = DesignSystemColor.primaryText + + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + required init?(coder: NSCoder) { + super.init(coder: coder) + } +} + +private extension WorkoutCardCell { + func setupConstraints() { + contentView.addSubview(workoutIconDescriptionLabel) + workoutIconDescriptionLabel.bottomAnchor + .constraint(equalTo: contentView.bottomAnchor, constant: -12).isActive = true + workoutIconDescriptionLabel.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true + workoutIconDescriptionLabel.trailingAnchor + .constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true + + contentView.addSubview(workoutIcon) + workoutIcon.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true + workoutIcon.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true + workoutIcon.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true + workoutIcon.bottomAnchor.constraint(equalTo: workoutIconDescriptionLabel.topAnchor, constant: -15).isActive = true + } + + func makeShadowAndRounded() { + let radius: CGFloat = 10 + contentView.layer.cornerRadius = radius + contentView.layer.borderWidth = 1 + contentView.layer.borderColor = UIColor.clear.cgColor + contentView.layer.masksToBounds = true + + layer.shadowColor = UIColor.black.cgColor + layer.shadowOffset = CGSize(width: 0, height: 1.0) + layer.shadowRadius = 2.0 + layer.shadowOpacity = 0.5 + layer.masksToBounds = false + layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath + layer.cornerRadius = radius + } + + func makeSelectUI() { + workoutIcon.tintColor = DesignSystemColor.main03 + workoutIcon.makeShadow() + + workoutIconDescriptionLabel.textColor = DesignSystemColor.main03 + workoutIconDescriptionLabel.font = .preferredFont(forTextStyle: .title3, with: .traitBold) + } + + func makeDeslectUI() { + workoutIcon.tintColor = DesignSystemColor.primaryText + workoutIcon.disableShadow() + + workoutIconDescriptionLabel.textColor = DesignSystemColor.primaryText + workoutIconDescriptionLabel.font = .preferredFont(forTextStyle: .title3) + } +} + +private extension UIImageView { + func makeShadow() { + layer.shadowColor = UIColor.black.cgColor + layer.shadowOffset = CGSize(width: -2, height: 2) + layer.shadowRadius = 2.0 + layer.shadowOpacity = 0.3 + layer.masksToBounds = false + } + + func disableShadow() { + layer.shadowOpacity = 0 + } +} diff --git a/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutEnvironmentSetupViewController.swift b/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutEnvironmentSetupViewController.swift new file mode 100644 index 00000000..b1b55dc7 --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutEnvironmentSetupViewController.swift @@ -0,0 +1,90 @@ +// +// WorkoutEnvironmentSetupViewController.swift +// RecordFeature +// +// Created by MaraMincho on 11/15/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - WorkoutEnvironmentSetupViewController + +public final class WorkoutEnvironmentSetupViewController: UIViewController { + override public func viewDidLoad() { + super.viewDidLoad() + setup() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + insertTempSource() + } + + lazy var contentNAV: UINavigationController = { + let nav = UINavigationController(rootViewController: workoutSelectView) + + return nav + }() + + private let workoutSelectView = WorkoutSelectViewController() + + private let pageControl: GWPageControl = { + let pageControl = GWPageControl(count: Constant.countOfPage) + + pageControl.translatesAutoresizingMaskIntoConstraints = false + return pageControl + }() + + var dataSource: UICollectionViewDiffableDataSource! + var workoutCardCollectionView: UICollectionView! +} + +private extension WorkoutEnvironmentSetupViewController { + func setup() { + view.backgroundColor = .systemBackground + setupViewHierarchyAndConstraints() + workoutCardCollectionView = workoutSelectView.workoutCardCollectionView + + configureDataSource() + } + + func configureDataSource() { + dataSource = .init(collectionView: workoutCardCollectionView, cellProvider: { collectionView, indexPath, _ in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WorkoutCardCell.identifier, for: indexPath) + return cell + }) + } + + func insertTempSource() { + var snapshot = dataSource.snapshot() + snapshot.deleteAllItems() + snapshot.appendSections([0]) + snapshot.appendItems([.init(), .init(), .init(), .init(), .init()]) + + dataSource.apply(snapshot) + } + + func setupViewHierarchyAndConstraints() { + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(pageControl) + pageControl.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 10).isActive = true + pageControl.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 23).isActive = true + pageControl.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -23).isActive = true + pageControl.heightAnchor.constraint(equalToConstant: 30).isActive = true + + view.addSubview(contentNAV.view) + contentNAV.view.translatesAutoresizingMaskIntoConstraints = false + contentNAV.view.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true + contentNAV.view.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true + contentNAV.view.topAnchor.constraint(equalTo: pageControl.bottomAnchor).isActive = true + contentNAV.view.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true + } + + enum Constant { + static let countOfPage = 2 + } +} diff --git a/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutSelectViewController.swift b/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutSelectViewController.swift new file mode 100644 index 00000000..a0aaf22a --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/WorkoutSelectScene/View/WorkoutSelectViewController.swift @@ -0,0 +1,101 @@ +// +// WorkoutSelectViewController.swift +// RecordFeature +// +// Created by MaraMincho on 11/16/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - WorkoutSelectViewController + +final class WorkoutSelectViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + setupConstraints() + navigationController?.setNavigationBarHidden(true, animated: false) + } + + private let workoutSelectDescriptionLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .title1, with: .traitBold) + label.textAlignment = .left + label.text = "1. 운동을 선택하세요" + + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + lazy var workoutCardCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout()) + collectionView.register(WorkoutCardCell.self, forCellWithReuseIdentifier: WorkoutCardCell.identifier) + + collectionView.translatesAutoresizingMaskIntoConstraints = false + return collectionView + }() + + private let nextButton: UIButton = { + let button = UIButton() + button.configurationUpdateHandler = UIButton.Configuration.main(label: "다음") + + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() +} + +private extension WorkoutSelectViewController { + func makeCollectionViewLayout() -> UICollectionViewLayout { + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1)) + + let item = NSCollectionLayoutItem(layoutSize: itemSize) + item.contentInsets = .init( + top: Const.cellInsets, + leading: Const.cellInsets, + bottom: Const.cellInsets, + trailing: Const.cellInsets + ) + + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalWidth(0.55)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + + return UICollectionViewCompositionalLayout(section: section) + } + + func setupConstraints() { + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(workoutSelectDescriptionLabel) + workoutSelectDescriptionLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true + workoutSelectDescriptionLabel.leadingAnchor + .constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true + workoutSelectDescriptionLabel.trailingAnchor + .constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true + + view.addSubview(workoutCardCollectionView) + workoutCardCollectionView.topAnchor + .constraint(equalTo: workoutSelectDescriptionLabel.bottomAnchor, constant: 12).isActive = true + workoutCardCollectionView.leadingAnchor + .constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true + workoutCardCollectionView.trailingAnchor + .constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true + workoutCardCollectionView.bottomAnchor + .constraint(equalTo: view.bottomAnchor, constant: -15).isActive = true + + view.addSubview(nextButton) + nextButton.leadingAnchor + .constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true + nextButton.trailingAnchor + .constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true + nextButton.bottomAnchor + .constraint(equalTo: safeArea.bottomAnchor, constant: -28).isActive = true + } + + enum Const { + static let cellInsets: CGFloat = 5 + } +} diff --git a/iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift b/iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift new file mode 100644 index 00000000..f9e1f63d --- /dev/null +++ b/iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift @@ -0,0 +1,13 @@ +// +// ConstraintsGuideLine.swift +// DesignSystem +// +// Created by MaraMincho on 11/18/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import Foundation + +public enum ConstraintsGuideLine { + public static let value: CGFloat = 23 +}