diff --git a/FLINT/Data/Sources/Networking/API/TermsAPI.swift b/FLINT/Data/Sources/Networking/API/TermsAPI.swift
new file mode 100644
index 00000000..8e6a878d
--- /dev/null
+++ b/FLINT/Data/Sources/Networking/API/TermsAPI.swift
@@ -0,0 +1,16 @@
+//
+// TermsAPI.swift
+// Data
+//
+// Created by 김호성 on 2026.05.29.
+//
+
+import Foundation
+
+import Moya
+
+public enum TermsAPI {
+ case getTerms
+ case getTerm(id: String)
+ case agreeTerms(ids: [String])
+}
diff --git a/FLINT/Domain/Sources/Entity/Term/SignUpTerm.swift b/FLINT/Domain/Sources/Entity/Term/SignUpTerm.swift
new file mode 100644
index 00000000..0acff5d9
--- /dev/null
+++ b/FLINT/Domain/Sources/Entity/Term/SignUpTerm.swift
@@ -0,0 +1,68 @@
+//
+// SignUpTerm.swift
+// Domain
+//
+// Created by 김호성 on 2026.06.10.
+//
+
+import Foundation
+
+public enum SignUpTerm: String, Sendable, CaseIterable {
+ case service = "SERVICE"
+ case privacy = "PRIVACY"
+}
+
+extension SignUpTerm {
+ public init?(id: Int) {
+ switch id {
+ case 1:
+ self = .service
+ case 2:
+ self = .privacy
+ default:
+ return nil
+ }
+ }
+
+ public var id: Int {
+ switch self {
+ case .service:
+ return 1
+ case .privacy:
+ return 2
+ }
+ }
+
+ public var title: String {
+ switch self {
+ case .service:
+ return "서비스 이용 약관 동의"
+ case .privacy:
+ return "개인정보 처리 방침 동의"
+ }
+ }
+
+ public var description: String {
+ switch self {
+ case .service:
+ return """
+ 본 약관은 서비스 이용과 관련한 기본적인 권리·의무 및 책임사항을 규정합니다.
+ """
+ case .privacy:
+ return """
+ 서비스 제공을 위해 개인정보를 수집 · 이용합니다.
콘텐츠 추천, 컬렉션 생성 및 공유, 맞춤형 탐색 경험 제공을 위한 이용 기록 및 취향 정보 처리 내용이 포함됩니다.
+
+ 수집 항목 : 계정 정보, 취향 정보, 컬렉션 및 콘텐츠 활동, 서비스 이용 기록 등
+
+ 수집 목적: 개인화 추천 제공, 컬렉션 생성 및 공유, 서비스 운영 및 이용자 보호
+ """
+ }
+ }
+
+ public var isRequired: Bool {
+ switch self {
+ case .service, .privacy:
+ return true
+ }
+ }
+}
diff --git a/FLINT/FLINT/Dependency/Factory/ViewController/NicknameViewControllerFactory+.swift b/FLINT/FLINT/Dependency/Factory/ViewController/NicknameViewControllerFactory+.swift
index 89c8fcda..901bba01 100644
--- a/FLINT/FLINT/Dependency/Factory/ViewController/NicknameViewControllerFactory+.swift
+++ b/FLINT/FLINT/Dependency/Factory/ViewController/NicknameViewControllerFactory+.swift
@@ -9,8 +9,8 @@ import Foundation
import Presentation
-extension NicknameViewControllerFactory where Self: OnboardingViewModelFactory & ViewControllerFactory {
- func makeNicknameViewController() -> NicknameViewController {
- return NicknameViewController(onboardingViewModel: makeOnboardingViewModel(), viewControllerFactory: self)
+extension NicknameViewControllerFactory where Self: ViewControllerFactory {
+ func makeNicknameViewController(onboardingViewModel: OnboardingViewModel) -> NicknameViewController {
+ return NicknameViewController(onboardingViewModel: onboardingViewModel, viewControllerFactory: self)
}
}
diff --git a/FLINT/FLINT/Dependency/Factory/ViewController/TermsAgreementViewControllerFactory+.swift b/FLINT/FLINT/Dependency/Factory/ViewController/TermsAgreementViewControllerFactory+.swift
new file mode 100644
index 00000000..e50be062
--- /dev/null
+++ b/FLINT/FLINT/Dependency/Factory/ViewController/TermsAgreementViewControllerFactory+.swift
@@ -0,0 +1,16 @@
+//
+// TermsAgreementViewControllerFactory+.swift
+// FLINT
+//
+// Created by 김호성 on 2026.05.29.
+//
+
+import Foundation
+
+import Presentation
+
+extension TermsAgreementViewControllerFactory where Self: OnboardingViewModelFactory & ViewControllerFactory {
+ func makeTermsAgreementViewController() -> TermsAgreementViewController {
+ return TermsAgreementViewController(onboardingViewModel: makeOnboardingViewModel(), viewControllerFactory: self)
+ }
+}
diff --git a/FLINT/Presentation/Package.resolved b/FLINT/Presentation/Package.resolved
new file mode 100644
index 00000000..9a14f68f
--- /dev/null
+++ b/FLINT/Presentation/Package.resolved
@@ -0,0 +1,60 @@
+{
+ "originHash" : "c95b4af140641978b2f57a74958c9190bb0ba5ebed92bff618c56503a9d2c924",
+ "pins" : [
+ {
+ "identity" : "alamofire",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Alamofire/Alamofire.git",
+ "state" : {
+ "revision" : "7595cbcf59809f9977c5f6378500de2ad73b7ddb",
+ "version" : "5.12.0"
+ }
+ },
+ {
+ "identity" : "kakao-ios-sdk",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/kakao/kakao-ios-sdk.git",
+ "state" : {
+ "revision" : "1a2b530921ab9d1f4385ced84109b7a86d42cff8",
+ "version" : "2.27.1"
+ }
+ },
+ {
+ "identity" : "kingfisher",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/onevcat/Kingfisher.git",
+ "state" : {
+ "revision" : "d30a5fad881137e2267f96a8e3fc35c58999bb94",
+ "version" : "8.6.2"
+ }
+ },
+ {
+ "identity" : "lottie-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/airbnb/lottie-ios.git",
+ "state" : {
+ "revision" : "a004050748dc197c56256a14dca49a035d74726c",
+ "version" : "4.5.2"
+ }
+ },
+ {
+ "identity" : "snapkit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/SnapKit/SnapKit.git",
+ "state" : {
+ "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4",
+ "version" : "5.7.1"
+ }
+ },
+ {
+ "identity" : "then",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/devxoul/Then.git",
+ "state" : {
+ "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a",
+ "version" : "3.0.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/FLINT/Presentation/Sources/View/Base/BaseCollectionViewListCell.swift b/FLINT/Presentation/Sources/View/Base/BaseCollectionViewListCell.swift
new file mode 100644
index 00000000..36179c56
--- /dev/null
+++ b/FLINT/Presentation/Sources/View/Base/BaseCollectionViewListCell.swift
@@ -0,0 +1,38 @@
+//
+// BaseCollectionViewListCell.swift
+// Presentation
+//
+// Created by 김호성 on 2026.06.01.
+//
+
+import UIKit
+
+public class BaseCollectionViewListCell: UICollectionViewListCell, ReuseIdentifiable {
+
+ // MARK: - Init
+
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+ setStyle()
+ setHierarchy()
+ setLayout()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: - Lifecycle
+
+ public override func prepareForReuse() {
+ super.prepareForReuse()
+ prepare()
+ }
+
+ // MARK: - Override Points
+
+ public func setStyle() { }
+ public func setHierarchy() { }
+ public func setLayout() { }
+ public func prepare() { }
+}
diff --git a/FLINT/Presentation/Sources/View/Component/FlintCheckbox.swift b/FLINT/Presentation/Sources/View/Component/FlintCheckbox.swift
new file mode 100644
index 00000000..1a8b1f8b
--- /dev/null
+++ b/FLINT/Presentation/Sources/View/Component/FlintCheckbox.swift
@@ -0,0 +1,22 @@
+//
+// FlintCheckbox.swift
+// Presentation
+//
+// Created by 김호성 on 2026.05.27.
+//
+
+import UIKit
+
+package final class FlintCheckbox: UIButton {
+
+ package init() {
+ super.init(frame: .zero)
+
+ setImage(.icCheckboxEmpty, for: .normal)
+ setImage(.icCheckboxFill, for: .selected)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/FLINT/Presentation/Sources/View/Resource/Assets.xcassets/Icon/Common/24/ic_up.imageset/Contents.json b/FLINT/Presentation/Sources/View/Resource/Assets.xcassets/Icon/Common/24/ic_up.imageset/Contents.json
new file mode 100644
index 00000000..139910c4
--- /dev/null
+++ b/FLINT/Presentation/Sources/View/Resource/Assets.xcassets/Icon/Common/24/ic_up.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "icon.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/FLINT/Presentation/Sources/View/Resource/Assets.xcassets/Icon/Common/24/ic_up.imageset/icon.svg b/FLINT/Presentation/Sources/View/Resource/Assets.xcassets/Icon/Common/24/ic_up.imageset/icon.svg
new file mode 100644
index 00000000..f0dc38f7
--- /dev/null
+++ b/FLINT/Presentation/Sources/View/Resource/Assets.xcassets/Icon/Common/24/ic_up.imageset/icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/FLINT/Presentation/Sources/View/Scene/Onboarding/TermsAgreement/Cell/TermAgreementCollectionViewCell.swift b/FLINT/Presentation/Sources/View/Scene/Onboarding/TermsAgreement/Cell/TermAgreementCollectionViewCell.swift
new file mode 100644
index 00000000..51796d14
--- /dev/null
+++ b/FLINT/Presentation/Sources/View/Scene/Onboarding/TermsAgreement/Cell/TermAgreementCollectionViewCell.swift
@@ -0,0 +1,130 @@
+//
+// TermAgreementCollectionViewCell.swift
+// Presentation
+//
+// Created by 김호성 on 2026.05.30.
+//
+
+import UIKit
+
+import Domain
+
+package final class TermAgreementCollectionViewCell: BaseCollectionViewListCell {
+
+ // MARK: - Component
+
+ package let termAgreeStackView = UIStackView().then {
+ $0.axis = .vertical
+ $0.spacing = 0
+ $0.alignment = .fill
+ $0.distribution = .equalSpacing
+ }
+
+ package let termAgreeHeaderView = UIView()
+ package let termAgreeCheckbox = FlintCheckbox()
+ package let termAgreeLabel = UILabel().then {
+ $0.textColor = .flintWhite
+ }
+
+ package lazy var expandButton = UIButton().then {
+ $0.setImage(.icDown, for: .normal)
+ $0.setImage(.icUp, for: .selected)
+ $0.addTarget(self, action: #selector(touchUpInsideExpandButton(_:)), for: .touchUpInside)
+ }
+
+ package let termDetailView = UIView().then {
+ $0.backgroundColor = .flintGray800
+ $0.layer.cornerRadius = 8
+ $0.isHidden = true
+ }
+ package let termDetailLabel = UILabel().then {
+ $0.textColor = .flintWhite
+ $0.numberOfLines = 0
+ }
+ package let termDetailMoreButton = UIButton().then {
+ $0.setAttributedTitle(
+ NSMutableAttributedString(.pretendard(.body2_r_14, text: "자세히 보기")).configured {
+ $0.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: $0.length))
+ },
+ for: .normal
+ )
+ $0.setTitleColor(.flintPrimary200, for: .normal)
+ }
+
+ // MARK: - Basic
+
+ package override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ contentView.backgroundColor = .flintBackground
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ package override func prepare() {
+ termAgreeCheckbox.removeTarget(nil, action: nil, for: .allEvents)
+ }
+
+ // MARK: - Setup
+
+ package override func setHierarchy() {
+ contentView.addSubview(termAgreeStackView)
+ termAgreeStackView.addArrangedSubviews(
+ termAgreeHeaderView,
+ termDetailView
+ )
+ termAgreeHeaderView.addSubviews(
+ termAgreeCheckbox,
+ termAgreeLabel,
+ expandButton,
+ )
+ termDetailView.addSubviews(
+ termDetailLabel,
+ termDetailMoreButton
+ )
+ }
+
+ package override func setLayout() {
+ termAgreeStackView.snp.makeConstraints {
+ $0.edges.equalToSuperview()
+ }
+ termAgreeCheckbox.snp.makeConstraints {
+ $0.size.equalTo(48)
+ $0.leading.verticalEdges.equalToSuperview()
+ }
+ termAgreeLabel.snp.makeConstraints {
+ $0.centerY.equalToSuperview()
+ $0.leading.equalTo(termAgreeCheckbox.snp.trailing)
+ }
+ expandButton.snp.makeConstraints {
+ $0.size.equalTo(48)
+ $0.trailing.verticalEdges.equalToSuperview()
+ }
+ termDetailLabel.snp.makeConstraints {
+ $0.top.horizontalEdges.equalToSuperview().inset(12)
+ }
+ termDetailMoreButton.snp.makeConstraints {
+ $0.top.equalTo(termDetailLabel.snp.bottom)
+ $0.height.equalTo(48)
+ $0.trailing.equalToSuperview().inset(12)
+ $0.bottom.equalToSuperview()
+ }
+ }
+
+ // MARK: - Public Function
+
+ package func configure(_ signUpTerm: SignUpTerm) {
+ termAgreeLabel.attributedText = .pretendard(.body1_r_16, text: signUpTerm.title)
+ termDetailLabel.attributedText = .pretendard(.body2_r_14, text: signUpTerm.description)
+ }
+
+ // MARK: - Private Function
+
+ @objc private func touchUpInsideExpandButton(_ sender: UIButton) {
+ sender.isSelected.toggle()
+ termDetailView.isHidden = !sender.isSelected
+ invalidateIntrinsicContentSize()
+ }
+}
diff --git a/FLINT/Presentation/Sources/View/Scene/Onboarding/TermsAgreement/TermsAgreementView.swift b/FLINT/Presentation/Sources/View/Scene/Onboarding/TermsAgreement/TermsAgreementView.swift
new file mode 100644
index 00000000..90b25168
--- /dev/null
+++ b/FLINT/Presentation/Sources/View/Scene/Onboarding/TermsAgreement/TermsAgreementView.swift
@@ -0,0 +1,117 @@
+//
+// TermsAgreementView.swift
+// Presentation
+//
+// Created by 김호성 on 2026.05.27.
+//
+
+import UIKit
+
+import SnapKit
+import Then
+
+public class TermsAgreementView: BaseView {
+
+ // MARK: - Component
+
+ package let titleLabel = UILabel().then {
+ $0.textColor = .flintWhite
+ $0.attributedText = .pretendard(.display2_m_28, text: "약관에 동의해주세요")
+ }
+
+ package let allAgreeStackView = UIStackView().then {
+ $0.axis = .horizontal
+ $0.spacing = 0
+ $0.alignment = .center
+ $0.distribution = .fill
+ }
+ package let allAgreeCheckbox = FlintCheckbox()
+ package let allAgreeLabel = UILabel().then {
+ $0.textColor = .flintWhite
+ $0.attributedText = .pretendard(.body1_r_16, text: "전체 동의")
+ }
+
+ package let separatorView = UIView().then {
+ $0.backgroundColor = .flintGray600
+ }
+
+ package let nextButton = FlintButton(style: .able, title: "동의하기").then {
+ $0.isEnabled = false
+ }
+
+ package let termAgreementsCollectionView: UICollectionView = {
+ var collectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .grouped)
+ collectionLayoutListConfiguration.backgroundColor = .flintBackground
+ collectionLayoutListConfiguration.showsSeparators = false
+ let collectionViewLayout: UICollectionViewCompositionalLayout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
+ let section = NSCollectionLayoutSection.list(using: collectionLayoutListConfiguration, layoutEnvironment: layoutEnvironment)
+ section.contentInsets = .init(top: 16, leading: 16, bottom: 12, trailing: 16)
+ section.interGroupSpacing = 8
+ return section
+ }
+ let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
+ collectionView.allowsSelection = false
+ return collectionView
+ }()
+
+ // MARK: - Basic
+
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+
+
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: - Setup
+
+ public override func setUI() {
+ backgroundColor = .flintBackground
+ }
+
+ public override func setHierarchy() {
+ addSubviews(
+ titleLabel,
+ allAgreeStackView,
+ separatorView,
+ termAgreementsCollectionView,
+ nextButton
+ )
+ allAgreeStackView.addArrangedSubviews(
+ allAgreeCheckbox,
+ allAgreeLabel,
+ )
+ }
+
+ public override func setLayout() {
+ titleLabel.snp.makeConstraints {
+ $0.top.equalToSuperview().inset(12)
+ $0.horizontalEdges.equalToSuperview().inset(16)
+ }
+ allAgreeStackView.snp.makeConstraints {
+ $0.top.equalTo(titleLabel.snp.bottom).offset(24)
+ $0.horizontalEdges.equalToSuperview().inset(16)
+ }
+ allAgreeCheckbox.snp.makeConstraints {
+ $0.size.equalTo(48)
+ }
+ separatorView.snp.makeConstraints {
+ $0.top.equalTo(allAgreeStackView.snp.bottom).offset(16)
+ $0.horizontalEdges.equalToSuperview()
+ $0.height.equalTo(4)
+ }
+ termAgreementsCollectionView.snp.makeConstraints {
+ $0.top.equalTo(separatorView.snp.bottom)
+ $0.horizontalEdges.equalToSuperview()
+ }
+ nextButton.snp.makeConstraints {
+ $0.top.equalTo(termAgreementsCollectionView.snp.bottom).offset(8)
+ $0.horizontalEdges.equalToSuperview().inset(16)
+ $0.height.equalTo(48)
+ $0.bottom.equalTo(safeAreaLayoutGuide)
+ }
+ }
+}
diff --git a/FLINT/Presentation/Sources/ViewController/Dependency/ViewControllerFactory.swift b/FLINT/Presentation/Sources/ViewController/Dependency/ViewControllerFactory.swift
index 7b5b09ac..2ed18798 100644
--- a/FLINT/Presentation/Sources/ViewController/Dependency/ViewControllerFactory.swift
+++ b/FLINT/Presentation/Sources/ViewController/Dependency/ViewControllerFactory.swift
@@ -18,6 +18,7 @@ public typealias ViewControllerFactory =
// MARK: - Onboarding
+ TermsAgreementViewControllerFactory &
NicknameViewControllerFactory &
ContentSelectViewControllerFactory &
// OttSelectViewControllerFactory &
diff --git a/FLINT/Presentation/Sources/ViewController/Scene/Login/LoginViewController.swift b/FLINT/Presentation/Sources/ViewController/Scene/Login/LoginViewController.swift
index 9c5df527..9585568b 100644
--- a/FLINT/Presentation/Sources/ViewController/Scene/Login/LoginViewController.swift
+++ b/FLINT/Presentation/Sources/ViewController/Scene/Login/LoginViewController.swift
@@ -61,8 +61,8 @@ public final class LoginViewController: BaseViewController {
}
private func register() {
- guard let nicknameViewController = viewControllerFactory?.makeNicknameViewController() else { return }
- navigationController?.pushViewController(nicknameViewController, animated: true)
+ guard let termsAgreementViewController = viewControllerFactory?.makeTermsAgreementViewController() else { return }
+ navigationController?.pushViewController(termsAgreementViewController, animated: true)
}
private func pushToTabBar() {
diff --git a/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/Nickname/NicknameViewController.swift b/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/Nickname/NicknameViewController.swift
index d5f44a48..09157c79 100644
--- a/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/Nickname/NicknameViewController.swift
+++ b/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/Nickname/NicknameViewController.swift
@@ -15,7 +15,7 @@ import View
import ViewModel
public protocol NicknameViewControllerFactory {
- func makeNicknameViewController() -> NicknameViewController
+ func makeNicknameViewController(onboardingViewModel: OnboardingViewModel) -> NicknameViewController
}
public final class NicknameViewController: BaseViewController {
diff --git a/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/TermsAgreement/TermsAgreementViewController.swift b/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/TermsAgreement/TermsAgreementViewController.swift
new file mode 100644
index 00000000..15f959b1
--- /dev/null
+++ b/FLINT/Presentation/Sources/ViewController/Scene/Onboarding/TermsAgreement/TermsAgreementViewController.swift
@@ -0,0 +1,117 @@
+//
+// TermsAgreementViewController.swift
+// Presentation
+//
+// Created by 김호성 on 2026.05.27.
+//
+
+import UIKit
+
+import Domain
+
+import View
+import ViewModel
+
+public protocol TermsAgreementViewControllerFactory {
+ func makeTermsAgreementViewController() -> TermsAgreementViewController
+}
+
+public final class TermsAgreementViewController: BaseViewController {
+
+ // MARK: - ViewModel
+
+ private let onboardingViewModel: OnboardingViewModel
+
+ // MARK: - DataSource
+
+ private var termAgreementsCollectionViewDataSource: TermAgreementsCollectionViewDataSource?
+
+ // MARK: - Basic
+
+ public init(onboardingViewModel: OnboardingViewModel, viewControllerFactory: ViewControllerFactory) {
+ self.onboardingViewModel = onboardingViewModel
+ super.init(nibName: nil, bundle: nil)
+ self.viewControllerFactory = viewControllerFactory
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func bind() {
+ onboardingViewModel.agreedTerms.sink { [weak self] agreedTerms in
+ guard let self else { return }
+ let isAllAgreed = agreedTerms.values.allSatisfy { $0 }
+ rootView.allAgreeCheckbox.isSelected = isAllAgreed
+ rootView.nextButton.isEnabled = isAllAgreed
+ termAgreementsCollectionViewDataSource?.apply(makeTermAgreementsCollectionViewSnapshot(), animatingDifferences: false)
+ }
+ .store(in: &cancellables)
+ }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+
+ setNavigationBar(.init(left: .back))
+
+ setupTermAgreementsCollectionView()
+
+ rootView.allAgreeCheckbox.addTarget(self, action: #selector(touchUpInsideAllAgreeCheckbox(_:)), for: .touchUpInside)
+ rootView.nextButton.addTarget(self, action: #selector(touchUpInsideNextButton(_:)), for: .touchUpInside)
+ }
+
+ @objc private func touchUpInsideAllAgreeCheckbox(_ sender: UIButton) {
+ let isAllAgreed = onboardingViewModel.agreedTerms.value.values.allSatisfy({ $0 })
+ onboardingViewModel.agreedTerms.value = onboardingViewModel.agreedTerms.value.mapValues { _ in !isAllAgreed }
+ }
+
+ @objc private func touchUpInsideNextButton(_ sender: UIButton) {
+ guard let nicknameViewController = viewControllerFactory?.makeNicknameViewController(onboardingViewModel: onboardingViewModel) else { return }
+ navigationController?.pushViewController(nicknameViewController, animated: true)
+ }
+}
+
+extension TermsAgreementViewController {
+
+ private typealias TermAgreementsCollectionViewDataSource = UICollectionViewDiffableDataSource
+ private typealias TermAgreementsCollectionViewSnapshot = NSDiffableDataSourceSnapshot
+
+ private enum TermAgreementsCollectionViewSection: Sendable {
+ case main
+ }
+
+ private enum TermAgreementsCollectionViewItem: Hashable, Sendable {
+ case term(SignUpTerm, agreed: Bool)
+ }
+
+ private func setupTermAgreementsCollectionView() {
+ let termAgreementCollectionViewCellRegistration = UICollectionView.CellRegistration { [weak self] cell, indexPath, item in
+ let signUpTerm = item.0
+ let agreed = item.1
+ cell.configure(signUpTerm)
+ cell.termAgreeCheckbox.isSelected = agreed
+ cell.termAgreeCheckbox.addAction(UIAction(handler: { [weak self, weak sender = cell.termAgreeCheckbox] action in
+ guard let self, let sender, let term = SignUpTerm(id: indexPath.row+1) else { return }
+ sender.isSelected.toggle()
+ onboardingViewModel.agreedTerms.value[term] = sender.isSelected
+ }), for: .touchUpInside)
+ }
+
+ rootView.termAgreementsCollectionView.dataSource = termAgreementsCollectionViewDataSource
+
+ termAgreementsCollectionViewDataSource = TermAgreementsCollectionViewDataSource(collectionView: rootView.termAgreementsCollectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
+ switch itemIdentifier {
+ case let .term(signUpTerm, agreed):
+ return collectionView.dequeueConfiguredReusableCell(using: termAgreementCollectionViewCellRegistration, for: indexPath, item: (signUpTerm, agreed: agreed))
+ }
+ })
+ termAgreementsCollectionViewDataSource?.apply(makeTermAgreementsCollectionViewSnapshot())
+ }
+
+ private func makeTermAgreementsCollectionViewSnapshot() -> TermAgreementsCollectionViewSnapshot {
+ var snapshot = TermAgreementsCollectionViewSnapshot()
+ snapshot.appendSections([.main])
+ snapshot.appendItems(SignUpTerm.allCases.map { .term($0, agreed: onboardingViewModel.agreedTerms.value[$0] ?? false) }, toSection: .main)
+ return snapshot
+ }
+}
diff --git a/FLINT/Presentation/Sources/ViewModel/Scene/Onboarding/OnboardingViewModel.swift b/FLINT/Presentation/Sources/ViewModel/Scene/Onboarding/OnboardingViewModel.swift
index 0ebce40b..69c091b3 100644
--- a/FLINT/Presentation/Sources/ViewModel/Scene/Onboarding/OnboardingViewModel.swift
+++ b/FLINT/Presentation/Sources/ViewModel/Scene/Onboarding/OnboardingViewModel.swift
@@ -27,8 +27,8 @@ public protocol OnboardingViewModelInput {
}
public protocol OnboardingViewModelOutput {
- // terms
- var agreedTermsIds: CurrentValueSubject<[String], Never> { get }
+ // term
+ var agreedTerms: CurrentValueSubject<[SignUpTerm: Bool], Never> { get }
// nickname
var nickname: CurrentValueSubject { get }
@@ -52,8 +52,10 @@ public final class DefaultOnboardingViewModel: OnboardingViewModel {
private let searchContentsUseCase: SearchContentsUseCase
private let signupUseCase: SignupUseCase
- #warning("TODO: - Temp. 약관 동의 구현 후 수정할 것!!!!")
- public let agreedTermsIds: CurrentValueSubject<[String], Never> = .init(["1", "2"])
+ public let agreedTerms: CurrentValueSubject<[SignUpTerm: Bool], Never> = .init([
+ .service: false,
+ .privacy: false,
+ ])
public let nickname: CurrentValueSubject = .init("")
public let nicknameValidState: CurrentValueSubject = .init(nil)
@@ -155,7 +157,10 @@ public final class DefaultOnboardingViewModel: OnboardingViewModel {
favoriteContentIds: selectedContents.value.compactMap({ content in
Int(content.id)
}),
- agreedTermsIds: agreedTermsIds.value
+ agreedTermsIds: agreedTerms.value
+ .filter { $0.value }
+ .map(\.key.id)
+ .map { String($0) }
)
)
.manageThread()