From d8a7e3ad8c3078818d6269082c2ff25e2044820a Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Mon, 3 Nov 2025 16:07:39 +0900 Subject: [PATCH 01/35] =?UTF-8?q?[=EC=B6=94=EA=B0=80]=20BaseVC=20=EB=B0=8F?= =?UTF-8?q?=20SOMTabBarC=EC=9D=98=20deinit=20=EC=8B=9C=20=EC=98=B5?= =?UTF-8?q?=EC=A0=80=EB=B2=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM/Base/BaseViewController.swift | 8 ++--- .../SOMTabBarController.swift | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/SOOUM/SOOUM/Base/BaseViewController.swift b/SOOUM/SOOUM/Base/BaseViewController.swift index 650b8af7..95137f10 100644 --- a/SOOUM/SOOUM/Base/BaseViewController.swift +++ b/SOOUM/SOOUM/Base/BaseViewController.swift @@ -38,13 +38,9 @@ class BaseViewController: UIViewController { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - /// Show deinit class name + /// show deinit class name and remove all observer deinit { - NotificationCenter.default.removeObserver( - self, - name: .hidesBottomBarWhenPushedDidChange, - object: nil - ) + NotificationCenter.default.removeObserver(self) Log.debug("Deinit: ", type(of: self).description().components(separatedBy: ".").last ?? "") } diff --git a/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarController.swift b/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarController.swift index 81e3f031..a5eeed55 100644 --- a/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarController.swift +++ b/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarController.swift @@ -10,6 +10,9 @@ import UIKit import SnapKit import Then + +// MARK: SOMTabBarControllerDelegate + protocol SOMTabBarControllerDelegate: AnyObject { func tabBarController( @@ -26,6 +29,9 @@ protocol SOMTabBarControllerDelegate: AnyObject { class SOMTabBarController: UIViewController { + + // MARK: Views + private lazy var tabBar = SOMTabBar().then { $0.delegate = self } @@ -34,6 +40,9 @@ class SOMTabBarController: UIViewController { $0.backgroundColor = .som.white } + + // MARK: Variables + var viewControllers: [UIViewController] = [] { didSet { self.tabBar.viewControllers = self.viewControllers } } @@ -43,6 +52,20 @@ class SOMTabBarController: UIViewController { weak var delegate: SOMTabBarControllerDelegate? + + // MARK: Deinitialize + + deinit { + NotificationCenter.default.removeObserver( + self, + name: .hidesBottomBarWhenPushedDidChange, + object: nil + ) + } + + + // MARK: Override func + override func viewDidLoad() { super.viewDidLoad() @@ -56,6 +79,9 @@ class SOMTabBarController: UIViewController { self.setupConstraints() } + + // MARK: Private func + private func setupConstraints() { self.view.addSubview(self.container) @@ -71,6 +97,9 @@ class SOMTabBarController: UIViewController { } } + + // MARK: Objc func + @objc private func hidesBottomBarWhenPushed(_ notification: Notification) { @@ -87,12 +116,18 @@ class SOMTabBarController: UIViewController { } } + + // MARK: Public func + func didSelectedIndex(_ index: Int) { self.tabBar.didSelectTabBarItem(index) } } + +// MARK: SOMTabBarDelegate + extension SOMTabBarController: SOMTabBarDelegate { func tabBar(_ tabBar: SOMTabBar, shouldSelectTabAt index: Int) -> Bool { From f82259d50c3c2ffdf0a5c13bfea574e297fcdbb3 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Mon, 3 Nov 2025 16:33:05 +0900 Subject: [PATCH 02/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8=20?= =?UTF-8?q?=EC=8B=9C=20api=202=EB=B2=88=20=ED=98=B8=EC=B6=9C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift b/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift index 23513f9f..386e01a6 100644 --- a/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift +++ b/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift @@ -14,7 +14,6 @@ extension UIRefreshControl { scrollView.contentInset.top = offset } self.beginRefreshing() - self.sendActions(for: .valueChanged) } func endRefreshingWithOffset() { From b89477288df12635b9e1cc5c44a73ddde0f20058 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Mon, 3 Nov 2025 16:33:17 +0900 Subject: [PATCH 03/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20pulse=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8A=94=20=EA=B2=83=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Home/Detail/DetailViewController.swift | 6 +----- .../Main/Home/Detail/DetailViewReactor.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index 8cc6b80d..6859155d 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -264,7 +264,7 @@ class DetailViewController: BaseNavigationViewController, View { .bind(to: reactor.action) .disposed(by: self.disposeBag) - let isRefreshing = reactor.state.map(\.isRefreshing).distinctUntilChanged().share() + let isRefreshing = reactor.state.map(\.isRefreshing).share() self.collectionView.refreshControl?.rx.controlEvent(.valueChanged) .withLatestFrom(isRefreshing) .filter { $0 == false } @@ -311,7 +311,6 @@ class DetailViewController: BaseNavigationViewController, View { .disposed(by: disposeBag) reactor.state.map(\.isLiked) - .distinctUntilChanged() .filter { $0 } .subscribe(with: self) { object, _ in NotificationCenter.default.post(name: .reloadData, object: object) @@ -319,7 +318,6 @@ class DetailViewController: BaseNavigationViewController, View { .disposed(by: self.disposeBag) reactor.state.map(\.isBlocked) - .distinctUntilChanged() .filter { $0 } .subscribe(with: self) { object, _ in @@ -340,8 +338,6 @@ class DetailViewController: BaseNavigationViewController, View { .disposed(by: self.disposeBag) reactor.state.map(\.isDeleted) - .filterNil() - .distinctUntilChanged() .filter { $0 } .subscribe(with: self) { object, _ in object.navigationBar.title = Text.deletedNavigationTitle diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift index b60496fb..e97e4e9b 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift @@ -36,17 +36,17 @@ class DetailViewReactor: Reactor { case updateIsRefreshing(Bool) case updateIsLiked(Bool) case updateIsBlocked(Bool) - case updateIsDeleted(Bool?) + case updateIsDeleted(Bool) case updateErrors(Int?) } struct State { fileprivate(set) var detailCard: DetailCardInfo? fileprivate(set) var commentCards: [BaseCardInfo] - fileprivate(set) var isRefreshing: Bool - fileprivate(set) var isLiked: Bool - fileprivate(set) var isBlocked: Bool - fileprivate(set) var isDeleted: Bool? + @Pulse fileprivate(set) var isRefreshing: Bool + @Pulse fileprivate(set) var isLiked: Bool + @Pulse fileprivate(set) var isBlocked: Bool + @Pulse fileprivate(set) var isDeleted: Bool fileprivate(set) var hasErrors: Int? } @@ -56,7 +56,7 @@ class DetailViewReactor: Reactor { isRefreshing: false, isLiked: false, isBlocked: false, - isDeleted: nil, + isDeleted: false, hasErrors: nil ) From 01cb4c0e39e68db79aed235f4c09a249aabc94b7 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Mon, 3 Nov 2025 16:47:30 +0900 Subject: [PATCH 04/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EC=9C=84=EB=A1=9C=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20=EC=8B=9C=20=ED=95=98=EB=8B=A8=20offset=20=EC=83=9D?= =?UTF-8?q?=EA=B8=B0=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentations/Main/Home/Detail/DetailViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index 6859155d..8375b12d 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -82,6 +82,8 @@ class DetailViewController: BaseNavigationViewController, View { $0.showsVerticalScrollIndicator = false $0.showsHorizontalScrollIndicator = false + $0.contentInsetAdjustmentBehavior = .never + $0.refreshControl = SOMRefreshControl() $0.register(DetailViewCell.self, forCellWithReuseIdentifier: "cell") From a4523cc8399d618ddaf82b6e557c72081e1b7078 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Mon, 3 Nov 2025 17:05:40 +0900 Subject: [PATCH 05/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=B0=8F?= =?UTF-8?q?=20=EB=8B=B5=EC=B9=B4=EB=93=9C=20=EC=88=98=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=20=EB=B0=A9=EB=B2=95=20=EB=B0=8F=20=EB=A6=AC=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=ED=9A=9F=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Detail/DetailViewController.swift | 7 --- .../Detail/Views/LikeAndCommentView.swift | 63 ++++++++++--------- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index 8375b12d..b1695a80 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -312,13 +312,6 @@ class DetailViewController: BaseNavigationViewController, View { } .disposed(by: disposeBag) - reactor.state.map(\.isLiked) - .filter { $0 } - .subscribe(with: self) { object, _ in - NotificationCenter.default.post(name: .reloadData, object: object) - } - .disposed(by: self.disposeBag) - reactor.state.map(\.isBlocked) .filter { $0 } .subscribe(with: self) { object, _ in diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift index 424952c1..f6adb10b 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift @@ -20,35 +20,25 @@ class LikeAndCommentView: UIView { // MARK: Views let likeBackgroundButton = UIButton() - private let likeContainer = UIStackView().then { - $0.axis = .horizontal - $0.alignment = .center - $0.distribution = .equalSpacing - $0.spacing = 4 - } + private let likeContainer = UIView() private let likeImageView = UIImageView().then { $0.image = .init(.icon(.v2(.outlined(.heart)))) $0.tintColor = .som.v2.gray500 } private let likeCountLabel = UILabel().then { $0.textColor = .som.v2.gray500 - $0.typography = .som.v2.caption1 + $0.typography = .som.v2.caption1.withAlignment(.left) } let commentBackgroundButton = UIButton() - private let commentContainer = UIStackView().then { - $0.axis = .horizontal - $0.alignment = .center - $0.distribution = .equalSpacing - $0.spacing = 4 - } + private let commentContainer = UIView() private let commentImageView = UIImageView().then { $0.image = .init(.icon(.v2(.outlined(.message_circle)))) $0.tintColor = .som.v2.gray500 } private let commentCountLabel = UILabel().then { $0.textColor = .som.v2.gray500 - $0.typography = .som.v2.caption1 + $0.typography = .som.v2.caption1.withAlignment(.left) } private let visitedLabel = UILabel().then { @@ -105,43 +95,54 @@ class LikeAndCommentView: UIView { $0.height.equalTo(44) } - let container = UIStackView(arrangedSubviews: [ - self.likeContainer, - self.commentContainer - ]).then { - $0.axis = .horizontal - $0.alignment = .center - $0.distribution = .equalSpacing - $0.spacing = 15 - } - self.addSubview(container) - container.snp.makeConstraints { + self.addSubview(self.likeContainer) + self.likeContainer.snp.makeConstraints { $0.centerY.equalToSuperview() $0.leading.equalToSuperview().offset(16) + $0.width.equalTo(60) } - - self.likeContainer.addArrangedSubviews(self.likeImageView, self.likeCountLabel) + self.likeContainer.addSubview(self.likeImageView) self.likeImageView.snp.makeConstraints { + $0.centerY.leading.equalToSuperview() $0.size.equalTo(20) } + self.likeContainer.addSubview(self.likeCountLabel) + self.likeCountLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.likeImageView.snp.trailing).offset(4) + } + self.addSubview(self.likeBackgroundButton) self.likeBackgroundButton.snp.makeConstraints { - $0.edges.equalTo(self.likeContainer) + $0.edges.equalTo(self.likeImageView) } - self.commentContainer.addArrangedSubviews(self.commentImageView, self.commentCountLabel) + self.addSubview(self.commentContainer) + self.commentContainer.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.likeContainer.snp.trailing) + $0.width.equalTo(60) + } + self.commentContainer.addSubview(self.commentImageView) self.commentImageView.snp.makeConstraints { + $0.centerY.leading.equalToSuperview() $0.size.equalTo(20) } + self.commentContainer.addSubview(self.commentCountLabel) + self.commentCountLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.commentImageView.snp.trailing).offset(4) + } + self.addSubview(self.commentBackgroundButton) self.commentBackgroundButton.snp.makeConstraints { - $0.edges.equalTo(self.commentContainer) + $0.edges.equalTo(self.commentImageView) } self.addSubview(self.visitedLabel) self.visitedLabel.snp.makeConstraints { $0.centerY.equalToSuperview() - $0.leading.greaterThanOrEqualTo(container.snp.trailing).offset(20) + $0.leading.greaterThanOrEqualTo(self.commentContainer.snp.trailing).offset(20) $0.trailing.equalToSuperview().offset(-20) } } From 3d4216dd72ba434c0122ca314b387ef2b921bded Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 16:02:20 +0900 Subject: [PATCH 06/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EB=B7=B0=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Home/Detail/Cells/DetailViewCell.swift | 11 +++++------ .../Main/Home/Detail/Views/LikeAndCommentView.swift | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift index 86c810dc..18a60e5b 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift @@ -141,13 +141,12 @@ class DetailViewCell: UICollectionViewCell { self.contentView.addSubview(self.memberInfoView) self.memberInfoView.snp.makeConstraints { - $0.top.equalToSuperview().offset(8) - $0.horizontalEdges.equalToSuperview() + $0.top.horizontalEdges.equalToSuperview() } self.contentView.addSubview(self.backgroundImageView) self.backgroundImageView.snp.makeConstraints { - $0.top.equalTo(self.memberInfoView.snp.bottom).offset(8) + $0.top.equalTo(self.memberInfoView.snp.bottom) $0.centerX.equalToSuperview() let size: CGFloat = UIScreen.main.bounds.width - 16 * 2 $0.size.equalTo(size) @@ -188,7 +187,7 @@ class DetailViewCell: UICollectionViewCell { self.contentView.addSubview(self.tags) self.tags.snp.makeConstraints { - $0.bottom.equalTo(backgroundImageView.snp.bottom).offset(-16) + $0.bottom.equalTo(self.backgroundImageView.snp.bottom).offset(-16) $0.leading.equalToSuperview().offset(16) $0.trailing.equalToSuperview().offset(-16) $0.height.equalTo(28) @@ -196,13 +195,13 @@ class DetailViewCell: UICollectionViewCell { self.contentView.addSubview(self.likeAndCommentView) self.likeAndCommentView.snp.makeConstraints { - $0.top.equalTo(self.backgroundImageView.snp.bottom).offset(12) + $0.top.equalTo(self.backgroundImageView.snp.bottom) $0.bottom.horizontalEdges.equalToSuperview() } self.contentView.addSubview(self.deletedCardInDetailBackgroundView) self.deletedCardInDetailBackgroundView.snp.makeConstraints { - $0.top.equalTo(self.memberInfoView.snp.bottom).offset(8) + $0.top.equalTo(self.memberInfoView.snp.bottom) $0.leading.equalToSuperview().offset(16) $0.bottom.trailing.equalToSuperview().offset(-16) } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift index f6adb10b..53db7858 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift @@ -97,7 +97,7 @@ class LikeAndCommentView: UIView { self.addSubview(self.likeContainer) self.likeContainer.snp.makeConstraints { - $0.centerY.equalToSuperview() + $0.verticalEdges.equalToSuperview() $0.leading.equalToSuperview().offset(16) $0.width.equalTo(60) } @@ -119,7 +119,7 @@ class LikeAndCommentView: UIView { self.addSubview(self.commentContainer) self.commentContainer.snp.makeConstraints { - $0.centerY.equalToSuperview() + $0.verticalEdges.equalToSuperview() $0.leading.equalTo(self.likeContainer.snp.trailing) $0.width.equalTo(60) } From 3f2b3771e23471cb3372f3e6f01bbefc28228260 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 16:02:42 +0900 Subject: [PATCH 07/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=20=EC=8B=9C=20offset=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift | 12 +----------- .../Main/Home/Detail/DetailViewController.swift | 8 ++++---- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift b/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift index 386e01a6..80664267 100644 --- a/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift +++ b/SOOUM/SOOUM/Extensions/Cocoa/UIRefreshControl.swift @@ -10,17 +10,7 @@ import UIKit extension UIRefreshControl { func beginRefreshingWithOffset(_ offset: CGFloat) { - if let scrollView: UIScrollView = superview as? UIScrollView { - scrollView.contentInset.top = offset - } + self.bounds.origin.y = -offset self.beginRefreshing() } - - func endRefreshingWithOffset() { - - if let scrollView: UIScrollView = superview as? UIScrollView { - scrollView.contentInset.top = 0 - } - self.endRefreshing() - } } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index b1695a80..ec626328 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -266,7 +266,7 @@ class DetailViewController: BaseNavigationViewController, View { .bind(to: reactor.action) .disposed(by: self.disposeBag) - let isRefreshing = reactor.state.map(\.isRefreshing).share() + let isRefreshing = reactor.state.map(\.isRefreshing).distinctUntilChanged().share() self.collectionView.refreshControl?.rx.controlEvent(.valueChanged) .withLatestFrom(isRefreshing) .filter { $0 == false } @@ -280,7 +280,7 @@ class DetailViewController: BaseNavigationViewController, View { .observe(on: MainScheduler.asyncInstance) .filter { $0 == false } .subscribe(with: self.collectionView) { collectionView, _ in - collectionView.refreshControl?.endRefreshingWithOffset() + collectionView.refreshControl?.endRefreshing() } .disposed(by: self.disposeBag) @@ -409,7 +409,7 @@ extension DetailViewController: UICollectionViewDataSource { guard let reactor = self.reactor else { return cell } - cell.likeAndCommentView.likeBackgroundButton.rx.throttleTap + cell.likeAndCommentView.likeBackgroundButton.rx.throttleTap(.seconds(3)) .withLatestFrom(reactor.state.compactMap(\.detailCard).map(\.isLike)) .subscribe(onNext: { isLike in reactor.action.onNext(.updateLike(isLike == false)) @@ -559,7 +559,7 @@ extension DetailViewController: UICollectionViewDelegateFlowLayout { if self.shouldRefreshing { self.collectionView.refreshControl?.beginRefreshingWithOffset( - self.detailCard.storyExpirationTime == nil ? 0 : 30 + self.detailCard.storyExpirationTime == nil ? 0 : 23 ) } } From 0ed227cecefe1b44f692f58e8ee6d8c8fd4e3949 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 16:03:12 +0900 Subject: [PATCH 08/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EC=9D=BD=EC=A7=80=20=EC=95=8A=EC=9D=80?= =?UTF-8?q?=20=EC=95=8C=EB=A6=BC=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationViewController.swift | 116 +++++++++++++----- .../NotificationViewReactor.swift | 96 ++++++++++++--- 2 files changed, 165 insertions(+), 47 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift index 7ddcb7ba..d98241b9 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift @@ -116,11 +116,6 @@ class NotificationViewController: BaseNavigationViewController, View { private var shouldRefreshing: Bool = false - // MARK: Variables + Rx - - private let willPushTypeAndCardId = PublishRelay<(detailType: DetailViewReactor.DetailType, id: String)?>() - - // MARK: Override func override func setupNaviBar() { @@ -150,19 +145,6 @@ class NotificationViewController: BaseNavigationViewController, View { func bind(reactor: NotificationViewReactor) { - self.willPushTypeAndCardId - .filterNil() - .distinctUntilChanged({ $0.detailType == $1.detailType && $0.id == $1.id }) - .subscribe(with: self) { object, detailInfo in - let detailViewController = DetailViewController() - detailViewController.reactor = reactor.reactorForDetail( - detailType: detailInfo.detailType, - with: detailInfo.id - ) - object.navigationPush(detailViewController, animated: true, bottomBarHidden: true) - } - .disposed(by: self.disposeBag) - // Action self.rx.viewDidLoad .map { _ in Reactor.Action.landing } @@ -195,6 +177,19 @@ class NotificationViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) + reactor.state.map(\.pushInfo) + .filterNil() + .distinctUntilChanged(reactor.canUpdatePushInfos) + .subscribe(with: self) { object, pushInfo in + let detailViewController = DetailViewController() + detailViewController.reactor = reactor.reactorForDetail( + detailType: pushInfo.detailType, + with: pushInfo.id + ) + object.navigationPush(detailViewController, animated: true, bottomBarHidden: true) + } + .disposed(by: self.disposeBag) + reactor.state.map { NotificationViewReactor.DisplayStates( displayType: $0.displayType, @@ -302,49 +297,110 @@ extension NotificationViewController: UITableViewDelegate { } case let .unread(notification): - var detailInfo: (detailType: DetailViewReactor.DetailType, id: String)? { + var pushOrRequestReadInfo: NotificationViewReactor.PushOrRequestReadInfo? { switch notification { case let .default(notification): + if case .feedLike = notification.notificationInfo.notificationType { - return (.feed, notification.targetCardId) + return .init( + detailType: .feed, + notificationId: notification.notificationInfo.notificationId, + targetCardId: notification.targetCardId, + shouldRead: true + ) } if case .commentLike = notification.notificationInfo.notificationType { - return (.comment, notification.targetCardId) + return .init( + detailType: .comment, + notificationId: notification.notificationInfo.notificationId, + targetCardId: notification.targetCardId, + shouldRead: true + ) } if case .commentWrite = notification.notificationInfo.notificationType { - return (.comment, notification.targetCardId) + return .init( + detailType: .comment, + notificationId: notification.notificationInfo.notificationId, + targetCardId: notification.targetCardId, + shouldRead: true + ) } return nil + /// follow, deleted, blocked 는 읽기 API만 호출 + case let .follow(notification): + + return .init( + detailType: .feed, + notificationId: notification.notificationInfo.notificationId, + targetCardId: nil, + shouldRead: true + ) + case let .deleted(notification): + + return .init( + detailType: .feed, + notificationId: notification.notificationInfo.notificationId, + targetCardId: nil, + shouldRead: true + ) + case let .blocked(notification): + + return .init( + detailType: .feed, + notificationId: notification.notificationInfo.notificationId, + targetCardId: nil, + shouldRead: true + ) default: + return nil } } - guard let detailInfo = detailInfo else { return } + guard let pushOrRequestReadInfo = pushOrRequestReadInfo else { return } - self.willPushTypeAndCardId.accept(detailInfo) + self.reactor?.action.onNext(.updatePushOrRequestReadInfo(pushOrRequestReadInfo)) case let .read(notification): - var detailInfo: (detailType: DetailViewReactor.DetailType, id: String)? { + var pushOrRequestReadInfo: NotificationViewReactor.PushOrRequestReadInfo? { switch notification { case let .default(notification): + if case .feedLike = notification.notificationInfo.notificationType { - return (.feed, notification.targetCardId) + return .init( + detailType: .feed, + notificationId: notification.notificationInfo.notificationId, + targetCardId: notification.targetCardId, + shouldRead: true + ) } if case .commentLike = notification.notificationInfo.notificationType { - return (.comment, notification.targetCardId) + return .init( + detailType: .comment, + notificationId: notification.notificationInfo.notificationId, + targetCardId: notification.targetCardId, + shouldRead: true + ) } if case .commentWrite = notification.notificationInfo.notificationType { - return (.comment, notification.targetCardId) + return .init( + detailType: .comment, + notificationId: notification.notificationInfo.notificationId, + targetCardId: notification.targetCardId, + shouldRead: true + ) } return nil default: + return nil } } - guard let detailInfo = detailInfo else { return } + guard let pushOrRequestReadInfo = pushOrRequestReadInfo else { return } + + self.reactor?.action.onNext(.updatePushOrRequestReadInfo(pushOrRequestReadInfo)) - self.willPushTypeAndCardId.accept(detailInfo) default: + return } } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift index d42783b6..98922d91 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift @@ -11,29 +11,13 @@ import Alamofire class NotificationViewReactor: Reactor { - struct DisplayStates { - let displayType: DisplayType - let unreads: [CompositeNotificationInfo]? - let reads: [CompositeNotificationInfo]? - let notices: [NoticeInfo]? - } - - enum DisplayType: Equatable { - enum ActivityType: Equatable { - case unread - case read - } - - case activity(ActivityType) - case notice - } - enum Action: Equatable { case landing case refresh case updateDisplayType(DisplayType) case moreFind(lastId: String, displayType: DisplayType) case requestRead(String) + case updatePushOrRequestReadInfo(PushOrRequestReadInfo) } enum Mutation { @@ -42,6 +26,7 @@ class NotificationViewReactor: Reactor { case notices([NoticeInfo]) case moreNotices([NoticeInfo]) case updateDisplayType(DisplayType) + case updatePushOrRequestReadInfo((detailType: DetailViewReactor.DetailType, id: String)?) case updateIsRefreshing(Bool) case updateIsReadSuccess(Bool) } @@ -51,6 +36,7 @@ class NotificationViewReactor: Reactor { fileprivate(set) var notificationsForUnread: [CompositeNotificationInfo]? fileprivate(set) var notifications: [CompositeNotificationInfo]? fileprivate(set) var notices: [NoticeInfo]? + fileprivate(set) var pushInfo: (detailType: DetailViewReactor.DetailType, id: String)? fileprivate(set) var isRefreshing: Bool fileprivate(set) var isReadSuccess: Bool } @@ -69,6 +55,7 @@ class NotificationViewReactor: Reactor { notificationsForUnread: nil, notifications: nil, notices: nil, + pushInfo: nil, isRefreshing: false, isReadSuccess: false ) @@ -135,6 +122,44 @@ class NotificationViewReactor: Reactor { return self.notificationUseCase.requestRead(notificationId: selectedId) .map(Mutation.updateIsReadSuccess) + + case let .updatePushOrRequestReadInfo(pushOrRequestReadInfo): + + /// 읽은 알림 여부 확인 + if pushOrRequestReadInfo.shouldRead { + + return self.notificationUseCase.requestRead(notificationId: pushOrRequestReadInfo.notificationId) + .flatMapLatest { _ -> Observable in + + var concat: Observable { + /// 화면 전환할 카드 식별자 여부 확인 + if let targetCardId = pushOrRequestReadInfo.targetCardId { + return .concat([ + .just(.updatePushOrRequestReadInfo(nil)), + .just(.updatePushOrRequestReadInfo((pushOrRequestReadInfo.detailType, targetCardId))) + ]) + } else { + return .just(.updatePushOrRequestReadInfo(nil)) + } + } + + return concat + } + } else { + + var concat: Observable { + if let targetCardId = pushOrRequestReadInfo.targetCardId { + return .concat([ + .just(.updatePushOrRequestReadInfo(nil)), + .just(.updatePushOrRequestReadInfo((pushOrRequestReadInfo.detailType, targetCardId))) + ]) + } else { + return .just(.updatePushOrRequestReadInfo(nil)) + } + } + + return concat + } } } @@ -153,6 +178,8 @@ class NotificationViewReactor: Reactor { newState.notices? += notices case let .updateDisplayType(displayType): newState.displayType = displayType + case let .updatePushOrRequestReadInfo(pushInfo): + newState.pushInfo = pushInfo case let .updateIsRefreshing(isRefreshing): newState.isRefreshing = isRefreshing case let .updateIsReadSuccess(isReadSuccess): @@ -180,6 +207,33 @@ private extension NotificationViewReactor { } } +extension NotificationViewReactor { + + struct DisplayStates { + let displayType: DisplayType + let unreads: [CompositeNotificationInfo]? + let reads: [CompositeNotificationInfo]? + let notices: [NoticeInfo]? + } + + enum DisplayType: Equatable { + enum ActivityType: Equatable { + case unread + case read + } + + case activity(ActivityType) + case notice + } + + struct PushOrRequestReadInfo: Equatable { + let detailType: DetailViewReactor.DetailType + let notificationId: String + let targetCardId: String? + let shouldRead: Bool + } +} + extension NotificationViewReactor { var catchClosureNotis: ((Error) throws -> Observable ) { @@ -218,6 +272,14 @@ extension NotificationViewReactor { } } + func canUpdatePushInfos( + prev prevPushInfo: (detailType: DetailViewReactor.DetailType, id: String), + curr currPushInfo: (detailType: DetailViewReactor.DetailType, id: String) + ) -> Bool { + return prevPushInfo.detailType == currPushInfo.detailType && + prevPushInfo.id == currPushInfo.id + } + func canUpdateCells( prev prevStates: DisplayStates, curr currStates: DisplayStates From 25c4a6b30b36e8cf4c3eb7799b528786431e6f13 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 16:15:19 +0900 Subject: [PATCH 09/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EA=B8=B0=EB=B3=B8=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20border=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnboardingProfileImageSettingViewController.swift | 1 - .../Presentations/Main/Home/Detail/Views/MemberInfoView.swift | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift b/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift index ded15cfe..4e8d3d3b 100644 --- a/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift +++ b/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift @@ -60,7 +60,6 @@ class OnboardingProfileImageSettingViewController: BaseNavigationViewController, private let profileImageView = UIImageView().then { $0.image = .init(.image(.v2(.profile_large))) - // TODO: 임시, backgroundColor 넣어서 이미지 빈 곳 채움 $0.backgroundColor = .som.v2.gray300 $0.layer.cornerRadius = 120 * 0.5 $0.layer.borderWidth = 1 diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift index ce394814..1fd813fc 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift @@ -22,6 +22,9 @@ class MemberInfoView: UIView { /// 상세보기, 멤버 이미지 // let memberBackgroundButton = UIButton() private let memberImageView = UIImageView().then { + $0.backgroundColor = .som.v2.gray300 + $0.layer.borderColor = UIColor.som.v2.gray300.cgColor + $0.layer.borderWidth = 1 $0.layer.cornerRadius = 36 * 0.5 $0.clipsToBounds = true } From 3fb73f84bb0f08b91e5fe1ca6cfb183134c9bd57 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 16:43:13 +0900 Subject: [PATCH 10/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SOOUM/Domain/Models/DetailCardInfo.swift | 31 +++++++++++++++++++ .../Home/Detail/DetailViewController.swift | 31 ++++++++++++++++++- .../Main/Home/Detail/DetailViewReactor.swift | 13 +++----- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/SOOUM/SOOUM/Domain/Models/DetailCardInfo.swift b/SOOUM/SOOUM/Domain/Models/DetailCardInfo.swift index 91bdcff2..d404ae85 100644 --- a/SOOUM/SOOUM/Domain/Models/DetailCardInfo.swift +++ b/SOOUM/SOOUM/Domain/Models/DetailCardInfo.swift @@ -33,6 +33,37 @@ struct DetailCardInfo: Hashable { let prevCardImgURL: String? } +extension DetailCardInfo { + + func updateLikeCnt(_ likeCnt: Int, with isLike: Bool) -> DetailCardInfo { + + return DetailCardInfo( + id: self.id, + likeCnt: likeCnt, + commentCnt: self.commentCnt, + cardImgName: self.cardImgName, + cardImgURL: self.cardImgURL, + cardContent: self.cardContent, + font: self.font, + distance: self.distance, + createdAt: self.createdAt, + storyExpirationTime: self.storyExpirationTime, + isAdminCard: self.isAdminCard, + memberId: self.memberId, + nickname: self.nickname, + profileImgURL: self.profileImgURL, + isLike: isLike, + isCommentWritten: self.isCommentWritten, + tags: self.tags, + isOwnCard: self.isOwnCard, + visitedCnt: self.visitedCnt, + prevCardId: self.prevCardId, + isPrevCardDeleted: self.isPrevCardDeleted, + prevCardImgURL: self.prevCardImgURL + ) + } +} + extension DetailCardInfo { /// 작성된 태그 struct Tag: Hashable { diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index ec626328..61ea4513 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -310,10 +310,37 @@ class DetailViewController: BaseNavigationViewController, View { object.collectionView.reloadData() } } - .disposed(by: disposeBag) + .disposed(by: self.disposeBag) + + reactor.state.map(\.isLiked) + .distinctUntilChanged() + .filter { $0 } + .observe(on: MainScheduler.asyncInstance) + .subscribe(with: self) { object, _ in + + let updated: DetailCardInfo + if object.detailCard.isLike { + + let updatedLikeCnt = object.detailCard.likeCnt - 1 + updated = object.detailCard.updateLikeCnt(updatedLikeCnt, with: false) + } else { + + let updatedLikeCnt = object.detailCard.likeCnt + 1 + updated = object.detailCard.updateLikeCnt(updatedLikeCnt, with: true) + } + + object.detailCard = updated + + UIView.performWithoutAnimation { + object.collectionView.reloadData() + } + } + .disposed(by: self.disposeBag) reactor.state.map(\.isBlocked) + .distinctUntilChanged() .filter { $0 } + .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, _ in let title = Text.blockToastLeadingTitle + object.detailCard.nickname + Text.blockToastTrailingTitle @@ -333,7 +360,9 @@ class DetailViewController: BaseNavigationViewController, View { .disposed(by: self.disposeBag) reactor.state.map(\.isDeleted) + .distinctUntilChanged() .filter { $0 } + .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, _ in object.navigationBar.title = Text.deletedNavigationTitle object.navigationBar.setRightButtons([object.rightDeleteButton]) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift index e97e4e9b..1083e69d 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift @@ -43,10 +43,10 @@ class DetailViewReactor: Reactor { struct State { fileprivate(set) var detailCard: DetailCardInfo? fileprivate(set) var commentCards: [BaseCardInfo] - @Pulse fileprivate(set) var isRefreshing: Bool - @Pulse fileprivate(set) var isLiked: Bool - @Pulse fileprivate(set) var isBlocked: Bool - @Pulse fileprivate(set) var isDeleted: Bool + fileprivate(set) var isRefreshing: Bool + fileprivate(set) var isLiked: Bool + fileprivate(set) var isBlocked: Bool + fileprivate(set) var isDeleted: Bool fileprivate(set) var hasErrors: Int? } @@ -122,10 +122,7 @@ class DetailViewReactor: Reactor { .filter { $0 } .withUnretained(self) .flatMapLatest { object, _ -> Observable in - return .concat([ - object.detailCard(), - .just(.updateIsLiked(true)) - ]) + return .just(.updateIsLiked(true)) } ]) } From 5de4014179504d5fe3b27d50321db1fc0f934f0d Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 18:10:27 +0900 Subject: [PATCH 11/35] =?UTF-8?q?[=EC=B6=94=EA=B0=80]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EC=B0=A8=EB=8B=A8=20=EB=B0=8F=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=B0=8F=20=EB=B2=84=ED=8A=BC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/SOMBottomFloatView.swift | 5 ++++ .../DesignSystem/Components/SOMButton.swift | 29 +++++++++++++++++-- .../Foundations/UIImage+SOOUM.swift | 1 + .../v2_eye_outlined.imageset/Contents.json | 16 ++++++++++ .../v2_eye_outlined.svg | 4 +++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/Contents.json create mode 100644 SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/v2_eye_outlined.svg diff --git a/SOOUM/SOOUM/DesignSystem/Components/SOMBottomFloatView.swift b/SOOUM/SOOUM/DesignSystem/Components/SOMBottomFloatView.swift index 22fcaf6b..872a7c19 100644 --- a/SOOUM/SOOUM/DesignSystem/Components/SOMBottomFloatView.swift +++ b/SOOUM/SOOUM/DesignSystem/Components/SOMBottomFloatView.swift @@ -91,6 +91,8 @@ private extension SOMBottomFloatView { $0.contentHorizontalAlignment = .left + $0.isEnabled = action.isEnabled + $0.tag = action.tag $0.addTarget(self, action: #selector(self.tap(_:)), for: .touchUpInside) } @@ -116,6 +118,7 @@ extension SOMBottomFloatView { let tag: Int let image: UIImage? let foregroundColor: UIColor + let isEnabled: Bool let title: String let action: (() -> Void) @@ -123,12 +126,14 @@ extension SOMBottomFloatView { title: String, image: UIImage? = nil, foregroundColor: UIColor = .som.v2.gray500, + isEnabled: Bool = true, action: @escaping (() -> Void) ) { self.tag = UUID().hashValue self.title = title self.image = image self.foregroundColor = foregroundColor + self.isEnabled = isEnabled self.action = action } } diff --git a/SOOUM/SOOUM/DesignSystem/Components/SOMButton.swift b/SOOUM/SOOUM/DesignSystem/Components/SOMButton.swift index 93a107a3..c66e189e 100644 --- a/SOOUM/SOOUM/DesignSystem/Components/SOMButton.swift +++ b/SOOUM/SOOUM/DesignSystem/Components/SOMButton.swift @@ -95,7 +95,14 @@ private extension SOMButton { updatedConfig?.background.backgroundColor = self.backgroundColor updatedConfig?.background.backgroundColorTransformer = UIConfigurationColorTransformer { _ in // 비활성화 상태일 때, backgroundColor - if button.isEnabled == false { return .som.v2.gray200 } + if button.isEnabled == false { + switch self.backgroundColor { + case .som.v2.black: return .som.v2.gray200 + case .som.v2.gray100: return .som.v2.gray200 + case .som.v2.white: return .som.v2.white + default: return .clear + } + } // 선택된 상태일 때, backgroundColor if button.isSelected { return .som.v2.pLight1 } // 하이라이트 상태일 때, backgroundColor @@ -115,7 +122,14 @@ private extension SOMButton { updatedConfig?.background.strokeColor = self.backgroundColor ?? .clear updatedConfig?.background.strokeColorTransformer = UIConfigurationColorTransformer { _ in // 비활성화 상태일 때, backgroundColor - if button.isEnabled == false { return .som.v2.gray200 } + if button.isEnabled == false { + switch self.backgroundColor { + case .som.v2.black: return .som.v2.gray200 + case .som.v2.gray100: return .som.v2.gray200 + case .som.v2.white: return .som.v2.white + default: return .clear + } + } // 선택된 상태일 때, backgroundColor if button.isSelected { return .som.v2.pMain } // 하이라이트 상태일 때, backgroundColor @@ -141,7 +155,16 @@ private extension SOMButton { func applyConfiguration(to configuration: inout UIButton.Configuration?) { var foregroundColor: UIColor { - return self.isEnabled ? (self.foregroundColor ?? .som.v2.white) : .som.v2.gray400 + if self.isEnabled == false { + switch self.foregroundColor { + case .som.v2.white: return .som.v2.gray400 + case .som.v2.gray600: return .som.v2.gray400 + case .som.v2.gray500: return .som.v2.gray300 + default: return .som.v2.gray300 + } + } + + return self.foregroundColor ?? .som.v2.white } if let image = self.image { diff --git a/SOOUM/SOOUM/DesignSystem/Foundations/UIImage+SOOUM.swift b/SOOUM/SOOUM/DesignSystem/Foundations/UIImage+SOOUM.swift index 08ea7cb2..661ac12d 100644 --- a/SOOUM/SOOUM/DesignSystem/Foundations/UIImage+SOOUM.swift +++ b/SOOUM/SOOUM/DesignSystem/Foundations/UIImage+SOOUM.swift @@ -178,6 +178,7 @@ extension UIImage.SOOUMType { case delete case down case error + case eye case flag case hash case heart diff --git a/SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/Contents.json b/SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/Contents.json new file mode 100644 index 00000000..de4398ae --- /dev/null +++ b/SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "v2_eye_outlined.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/v2_eye_outlined.svg b/SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/v2_eye_outlined.svg new file mode 100644 index 00000000..9d2ecb9e --- /dev/null +++ b/SOOUM/SOOUM/Resources/Assets.xcassets/DesignSystem/V2/Icons/Outlined/v2_eye_outlined.imageset/v2_eye_outlined.svg @@ -0,0 +1,4 @@ + + + + From b23f693dbcb5c23ed6797d39e5cd98969c39eeb8 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 18:10:58 +0900 Subject: [PATCH 12/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=B0=A8=EB=8B=A8?= =?UTF-8?q?=20=EB=B0=8F=20=EC=8B=A0=EA=B3=A0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SOOUM/Extensions/Cocoa/Notification.swift | 2 + .../Home/Detail/DetailViewController.swift | 221 ++++++++++-------- .../Main/Home/Detail/DetailViewReactor.swift | 48 +++- .../Detail/Report/ReportViewController.swift | 13 +- .../Detail/Report/ReportViewReactor.swift | 21 +- 5 files changed, 198 insertions(+), 107 deletions(-) diff --git a/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift b/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift index 9c0855d2..ce0a6315 100644 --- a/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift +++ b/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift @@ -18,4 +18,6 @@ extension Notification.Name { static let scollingToTopWithAnimation = Notification.Name("scollingToTopWithAnimation") /// Should reload static let reloadData = Notification.Name("reloadData") + /// Updated report state + static let updatedReportState = Notification.Name("updatedReportState") } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index 61ea4513..a809fc1a 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -34,6 +34,7 @@ class DetailViewController: BaseNavigationViewController, View { static let bottomToastEntryName: String = "bottomToastEntryName" static let blockButtonFloatActionTitle: String = "차단하기" + static let unblockButtonFloatActionTitle: String = "차단해제" static let reportButtonFloatActionTitle: String = "신고하기" static let deleteButtonFloatActionTitle: String = "삭제" @@ -112,6 +113,8 @@ class DetailViewController: BaseNavigationViewController, View { private var currentOffset: CGFloat = 0 private var isRefreshEnabled: Bool = true private var shouldRefreshing: Bool = false + + private var actions: [SOMBottomFloatView.FloatAction] = [] // MARK: Override func @@ -125,6 +128,13 @@ class DetailViewController: BaseNavigationViewController, View { name: .reloadData, object: nil ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(self.updatedReportState(_:)), + name: .updatedReportState, + object: nil + ) } override func setupNaviBar() { @@ -173,60 +183,95 @@ class DetailViewController: BaseNavigationViewController, View { } } .disposed(by: self.disposeBag) + } + + + // MARK: - Bind + + func bind(reactor: DetailViewReactor) { - self.rightMoreButton.rx.throttleTap + // 답카드 작성 전환 + self.floatingButton.backgoundButton.rx.throttleTap + .subscribe(with: self) { object, _ in + let writeCardViewController = WriteCardViewController() + writeCardViewController.reactor = reactor.reactorForWriteCard() + object.navigationPush(writeCardViewController, animated: true, bottomBarHidden: true) + } + .disposed(by: self.disposeBag) + + let detailCard = reactor.state.map(\.detailCard).filterNil().distinctUntilChanged() + let isBlocked = reactor.state.map(\.isBlocked).distinctUntilChanged() + let isReported = reactor.state.map(\.isReported).distinctUntilChanged() + + let rightMoreButtonDidTap = self.rightMoreButton.rx.throttleTap.share() + // 더보기 버튼 액션 + rightMoreButtonDidTap + .withLatestFrom(detailCard) + .filter { $0.isOwnCard } .subscribe(with: self) { object, _ in - var actions: [SOMBottomFloatView.FloatAction] { - - if object.detailCard.isOwnCard { - - return [ - .init( - title: Text.deleteButtonFloatActionTitle, - image: .init(.icon(.v2(.outlined(.trash)))), - foregroundColor: .som.v2.rMain, - action: { [weak object] in - SwiftEntryKit.dismiss(.specific(entryName: Text.bottomFloatEntryName)) { - - object?.showDeleteCardDialog() - } - } - ) - ] - } else { - - return [ - .init( - title: Text.blockButtonFloatActionTitle, - image: .init(.icon(.v2(.outlined(.hide)))), - action: { [weak object] in - SwiftEntryKit.dismiss(.specific(entryName: Text.bottomFloatEntryName)) { - - object?.showBlockedUserDialog() - } - } - ), - .init( - title: Text.reportButtonFloatActionTitle, - image: .init(.icon(.v2(.outlined(.flag)))), - foregroundColor: .som.v2.rMain, - action: { [weak object] in - guard let object = object, let reactor = object.reactor else { return } - - SwiftEntryKit.dismiss(.specific(entryName: Text.bottomFloatEntryName)) { - - let reportViewController = ReportViewController() - reportViewController.reactor = reactor.reactorForReport() - object.navigationPush(reportViewController, animated: true, bottomBarHidden: true) - } + object.actions = [ + .init( + title: Text.deleteButtonFloatActionTitle, + image: .init(.icon(.v2(.outlined(.trash)))), + foregroundColor: .som.v2.rMain, + action: { [weak object] in + SwiftEntryKit.dismiss(.specific(entryName: Text.bottomFloatEntryName)) { + + object?.showDeleteCardDialog() + } + } + ) + ] + + let bottomFloatView = SOMBottomFloatView(actions: object.actions) + + var wrapper: SwiftEntryKitViewWrapper = bottomFloatView.sek + wrapper.entryName = Text.bottomFloatEntryName + wrapper.showBottomFloat(screenInteraction: .dismiss) + } + .disposed(by: self.disposeBag) + + rightMoreButtonDidTap + .withLatestFrom(Observable.combineLatest(detailCard, isBlocked, isReported)) + .filter { $0.0.isOwnCard == false } + .map { ($0.1, $0.2) } + .subscribe(with: self) { object, combined in + + let (isBlocked, isReported) = combined + + object.actions = [ + .init( + title: isBlocked ? Text.blockButtonFloatActionTitle : Text.unblockButtonFloatActionTitle, + image: .init(.icon(.v2(.outlined(isBlocked ? .hide : .eye)))), + action: { [weak object] in + SwiftEntryKit.dismiss(.specific(entryName: Text.bottomFloatEntryName)) { + if isBlocked { + object?.showBlockedUserDialog() + } else { + reactor.action.onNext(.block(isBlocked: false)) } - ) - ] - } - } + } + } + ), + .init( + title: Text.reportButtonFloatActionTitle, + image: .init(.icon(.v2(.outlined(.flag)))), + foregroundColor: .som.v2.rMain, + isEnabled: isReported == false, + action: { [weak object] in + + SwiftEntryKit.dismiss(.specific(entryName: Text.bottomFloatEntryName)) { + + let reportViewController = ReportViewController() + reportViewController.reactor = reactor.reactorForReport() + object?.navigationPush(reportViewController, animated: true, bottomBarHidden: true) + } + } + ) + ] - let bottomFloatView = SOMBottomFloatView(actions: actions) + let bottomFloatView = SOMBottomFloatView(actions: object.actions) var wrapper: SwiftEntryKitViewWrapper = bottomFloatView.sek wrapper.entryName = Text.bottomFloatEntryName @@ -234,6 +279,7 @@ class DetailViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) + // 카드 삭제 후 X 버튼 액션 self.rightDeleteButton.rx.throttleTap .subscribe(with: self) { object, _ in if let navigationController = object.navigationController { @@ -243,21 +289,6 @@ class DetailViewController: BaseNavigationViewController, View { } } .disposed(by: self.disposeBag) - } - - - // MARK: - Bind - - func bind(reactor: DetailViewReactor) { - - // 답카드 작성 전환 - self.floatingButton.backgoundButton.rx.throttleTap - .subscribe(with: self) { object, _ in - let writeCardViewController = WriteCardViewController() - writeCardViewController.reactor = reactor.reactorForWriteCard() - object.navigationPush(writeCardViewController, animated: true, bottomBarHidden: true) - } - .disposed(by: self.disposeBag) // Action @@ -284,9 +315,7 @@ class DetailViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) - reactor.state.map(\.detailCard) - .filterNil() - .distinctUntilChanged() + detailCard .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, detailCard in object.detailCard = detailCard @@ -337,9 +366,8 @@ class DetailViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) - reactor.state.map(\.isBlocked) - .distinctUntilChanged() - .filter { $0 } + isBlocked + .filter { $0 == false } .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, _ in @@ -377,28 +405,28 @@ class DetailViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) - reactor.state.map(\.hasErrors) - .filterNil() - .distinctUntilChanged() - .subscribe(with: self) { object, hasErrors in - - switch reactor.entranceType { - case .navi: - object.isDeleted = true - - UIView.performWithoutAnimation { - object.collectionView.reloadData() - } - case .push: - return - // let notificationTabBarController = NotificationTabBarController() - // notificationTabBarController.reactor = reactor.reactorForNoti() - // - // object.navigationPush(notificationTabBarController, animated: false) - // object.navigationController?.viewControllers.removeAll(where: { $0.isKind(of: DetailViewController.self) }) - } - } - .disposed(by: self.disposeBag) + // reactor.state.map(\.hasErrors) + // .filterNil() + // .distinctUntilChanged() + // .subscribe(with: self) { object, hasErrors in + // + // switch reactor.entranceType { + // case .navi: + // object.isDeleted = true + // + // UIView.performWithoutAnimation { + // object.collectionView.reloadData() + // } + // case .push: + // return + // let notificationTabBarController = NotificationTabBarController() + // notificationTabBarController.reactor = reactor.reactorForNoti() + // + // object.navigationPush(notificationTabBarController, animated: false) + // object.navigationController?.viewControllers.removeAll(where: { $0.isKind(of: DetailViewController.self) }) + // } + // } + // .disposed(by: self.disposeBag) } @@ -409,6 +437,12 @@ class DetailViewController: BaseNavigationViewController, View { self.reactor?.action.onNext(.landing) } + + @objc + private func updatedReportState(_ notification: Notification) { + + self.reactor?.action.onNext(.updateReport(true)) + } } extension DetailViewController: UICollectionViewDataSource { @@ -463,8 +497,9 @@ extension DetailViewController: UICollectionViewDataSource { object.navigationPop() } else { /// 없다면 새로운 viewController로 naviPush + guard let prevCardId = object.detailCard.prevCardId else { return } let detailViewController = DetailViewController() - detailViewController.reactor = reactor.reactorForPush(object.detailCard.id) + detailViewController.reactor = reactor.reactorForPush(prevCardId) object.navigationPush(detailViewController, animated: true, bottomBarHidden: true) } } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift index 1083e69d..7c88d986 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift @@ -27,6 +27,7 @@ class DetailViewReactor: Reactor { case delete case block(isBlocked: Bool) case updateLike(Bool) + case updateReport(Bool) } enum Mutation { @@ -35,8 +36,9 @@ class DetailViewReactor: Reactor { case moreComment([BaseCardInfo]) case updateIsRefreshing(Bool) case updateIsLiked(Bool) - case updateIsBlocked(Bool) case updateIsDeleted(Bool) + case updateReported(Bool) + case updateIsBlocked(Bool) case updateErrors(Int?) } @@ -45,8 +47,9 @@ class DetailViewReactor: Reactor { fileprivate(set) var commentCards: [BaseCardInfo] fileprivate(set) var isRefreshing: Bool fileprivate(set) var isLiked: Bool - fileprivate(set) var isBlocked: Bool fileprivate(set) var isDeleted: Bool + fileprivate(set) var isReported: Bool + fileprivate(set) var isBlocked: Bool fileprivate(set) var hasErrors: Int? } @@ -55,8 +58,9 @@ class DetailViewReactor: Reactor { commentCards: [], isRefreshing: false, isLiked: false, - isBlocked: false, isDeleted: false, + isReported: false, + isBlocked: true, hasErrors: nil ) @@ -113,7 +117,11 @@ class DetailViewReactor: Reactor { guard let memberId = self.currentState.detailCard?.memberId else { return .empty() } return self.cardUseCase.updateBlocked(id: memberId, isBlocked: isBlocked) - .map(Mutation.updateIsBlocked) + .flatMapLatest { isBlockedSuccess -> Observable in + /// isBlocked == true 일 때, 차단 요청 + return isBlockedSuccess ? .just(.updateIsBlocked(isBlocked == false)) : .empty() + } + .catch(self.catchClosure) case let .updateLike(isLike): return .concat([ @@ -124,7 +132,11 @@ class DetailViewReactor: Reactor { .flatMapLatest { object, _ -> Observable in return .just(.updateIsLiked(true)) } + .catch(self.catchClosure) ]) + case let .updateReport(isReported): + + return .just(.updateReported(isReported)) } } @@ -141,10 +153,12 @@ class DetailViewReactor: Reactor { newState.isRefreshing = isRefreshing case let .updateIsLiked(isLiked): newState.isLiked = isLiked - case let .updateIsBlocked(isBlocked): - newState.isBlocked = isBlocked case let .updateIsDeleted(isDeleted): newState.isDeleted = isDeleted + case let .updateReported(isReported): + newState.isReported = isReported + case let .updateIsBlocked(isBlocked): + newState.isBlocked = isBlocked case let .updateErrors(hasErrors): newState.hasErrors = hasErrors } @@ -232,12 +246,22 @@ extension DetailViewReactor { return { error in let nsError = error as NSError - return .concat([ - .just(.updateIsBlocked(false)), - .just(.updateIsDeleted(false)), - .just(.updateIsRefreshing(false)), - .just(.updateErrors(nsError.code)) - ]) + // errorCode == 409 일 때, 해당 사용자 중복 차단 + if case 409 = nsError.code { + return .concat([ + .just(.updateIsRefreshing(false)), + .just(.updateIsBlocked(false)) + ]) + } + + if case 410 = nsError.code { + return .concat([ + .just(.updateIsRefreshing(false)), + .just(.updateIsDeleted(true)) + ]) + } + + return .empty() } } } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewController.swift index 3435be38..8e3c8a54 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewController.swift @@ -137,6 +137,15 @@ class ReportViewController: BaseNavigationViewController, View { object.showSuccessReportedDialog() } .disposed(by: self.disposeBag) + + reactor.state.map(\.hasErrors) + .filter { $0 } + .subscribe(with: self) { object, _ in + object.navigationPop { + NotificationCenter.default.post(name: .updatedReportState, object: nil, userInfo: nil) + } + } + .disposed(by: self.disposeBag) } } @@ -182,7 +191,9 @@ private extension ReportViewController { style: .primary, action: { UIApplication.topViewController?.dismiss(animated: true) { - self.navigationPop() + self.navigationPop { + NotificationCenter.default.post(name: .updatedReportState, object: nil, userInfo: nil) + } } } ) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewReactor.swift index f61876b7..c6808266 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Report/ReportViewReactor.swift @@ -18,11 +18,13 @@ class ReportViewReactor: Reactor { case updateReportReason(ReportType?) /// 업로드 완료 여부 변경 case updateisReported(Bool) + case updateHasErrors(Bool) } struct State { fileprivate(set) var reportReason: ReportType? fileprivate(set) var isReported: Bool + fileprivate(set) var hasErrors: Bool } var initialState: State @@ -37,7 +39,7 @@ class ReportViewReactor: Reactor { self.cardUseCase = dependencies.rootContainer.resolve(CardUseCase.self) self.id = id - self.initialState = State(reportReason: nil, isReported: false) + self.initialState = State(reportReason: nil, isReported: false, hasErrors: false) } func mutate(action: Action) -> Observable { @@ -51,6 +53,7 @@ class ReportViewReactor: Reactor { return self.cardUseCase.reportCard(id: self.id, reportType: reportReason.rawValue) .map(Mutation.updateisReported) + .catch(self.catchClosure) } } @@ -61,7 +64,23 @@ class ReportViewReactor: Reactor { newState.reportReason = reportReason case let .updateisReported(isReported): newState.isReported = isReported + case let .updateHasErrors(hasErrors): + newState.hasErrors = hasErrors } return newState } } + +extension ReportViewReactor { + + var catchClosure: ((Error) throws -> Observable ) { + return { error in + + let nsError = error as NSError + switch nsError.code { + case 409, 410: return .just(.updateHasErrors(true)) + default: return .empty() + } + } + } +} From aca0feb94accee4b17ab39e1526dbdacf6e2aa2b Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 18:11:14 +0900 Subject: [PATCH 13/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=A0=84=ED=99=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Notification/NotificationViewController.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift index d98241b9..0805f36d 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift @@ -351,9 +351,6 @@ extension NotificationViewController: UITableViewDelegate { targetCardId: nil, shouldRead: true ) - default: - - return nil } } guard let pushOrRequestReadInfo = pushOrRequestReadInfo else { return } @@ -370,7 +367,7 @@ extension NotificationViewController: UITableViewDelegate { detailType: .feed, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, - shouldRead: true + shouldRead: false ) } if case .commentLike = notification.notificationInfo.notificationType { @@ -378,7 +375,7 @@ extension NotificationViewController: UITableViewDelegate { detailType: .comment, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, - shouldRead: true + shouldRead: false ) } if case .commentWrite = notification.notificationInfo.notificationType { @@ -386,7 +383,7 @@ extension NotificationViewController: UITableViewDelegate { detailType: .comment, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, - shouldRead: true + shouldRead: false ) } return nil From 8b9c5eb1518c3770a8f7cab4ef12c3d7c15c9742 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 18:21:12 +0900 Subject: [PATCH 14/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20API=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=8B=9C=20empty=20response=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/Remotes/NotificationRemoteDataSoruceImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOOUM/SOOUM/Data/Repositories/Remotes/NotificationRemoteDataSoruceImpl.swift b/SOOUM/SOOUM/Data/Repositories/Remotes/NotificationRemoteDataSoruceImpl.swift index a72a096e..aca942e8 100644 --- a/SOOUM/SOOUM/Data/Repositories/Remotes/NotificationRemoteDataSoruceImpl.swift +++ b/SOOUM/SOOUM/Data/Repositories/Remotes/NotificationRemoteDataSoruceImpl.swift @@ -32,7 +32,7 @@ class NotificationRemoteDataSoruceImpl: NotificationRemoteDataSource { func requestRead(notificationId: String) -> Observable { let request: NotificationRequest = .requestRead(notificationId: notificationId) - return self.provider.networkManager.perform(Int.self, request: request) + return self.provider.networkManager.perform(request) } func notices(lastId: String?, size: Int?) -> Observable { From 39ea83a3d52cc6fe7005640781003de62e1ec689 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Tue, 4 Nov 2025 18:21:39 +0900 Subject: [PATCH 15/35] =?UTF-8?q?[=EB=B2=84=EC=A0=84]=20Develop=201.17.1(1?= =?UTF-8?q?017010)=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SOOUM/SOOUM.xcodeproj/project.pbxproj b/SOOUM/SOOUM.xcodeproj/project.pbxproj index f2fe2c45..e4ff05d7 100644 --- a/SOOUM/SOOUM.xcodeproj/project.pbxproj +++ b/SOOUM/SOOUM.xcodeproj/project.pbxproj @@ -3788,7 +3788,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017000; + CURRENT_PROJECT_VERSION = 1017010; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3811,7 +3811,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.0; + MARKETING_VERSION = 1.17.1; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3840,7 +3840,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017000; + CURRENT_PROJECT_VERSION = 1017010; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3863,7 +3863,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.0; + MARKETING_VERSION = 1.17.1; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; From 17d5909d6f451853aa3bbdf630718211877ad67d Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Wed, 5 Nov 2025 16:32:08 +0900 Subject: [PATCH 16/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=ED=95=91=20=EC=8B=9C=20=EC=A4=84=EB=B0=94=EA=BF=88=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Write/Views/TextView/WriteCardTextView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Write/Views/TextView/WriteCardTextView.swift b/SOOUM/SOOUM/Presentations/Main/Write/Views/TextView/WriteCardTextView.swift index f87da320..4b914b82 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/Views/TextView/WriteCardTextView.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/Views/TextView/WriteCardTextView.swift @@ -50,7 +50,7 @@ class WriteCardTextView: UIView { $0.textContainerInset = .init(top: 20, left: 24, bottom: 20, right: 24) $0.textContainer.lineFragmentPadding = 0 - $0.scrollIndicatorInsets = .init(top: 4, left: 0, bottom: 4, right: 0) + $0.scrollIndicatorInsets = .init(top: 20, left: 0, bottom: 20, right: 0) $0.indicatorStyle = .white $0.isScrollEnabled = false @@ -188,13 +188,14 @@ class WriteCardTextView: UIView { attributes: attributes ) - let size: CGSize = .init(width: textView.bounds.width, height: .greatestFiniteMagnitude) - let textSize: CGSize = textView.sizeThatFits(size) + /// width 계산 시 textContainerInset 고려 + let textSize: CGSize = .init(width: textView.bounds.width - 24 * 2, height: .greatestFiniteMagnitude) var boundingHeight = attributedText.boundingRect( with: textSize, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil ).height + /// 자연스러운 줄바꿈을 위해 offset 추가 boundingHeight += 1.0 let lines: CGFloat = boundingHeight / self.typography.lineHeight From c31d8d5e483827390f371823f6db283d594cb05e Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Wed, 5 Nov 2025 16:45:42 +0900 Subject: [PATCH 17/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20UILabel,=20UITextF?= =?UTF-8?q?ield,=20UITextView=20=ED=83=80=EC=9D=B4=ED=8F=AC=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=BC=20=EC=84=A4=EC=A0=95=20=EC=8B=9C=20=ED=82=A4?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Typography/UILabel+Typography.swift | 42 ++++------------ .../Typography/UITextField+Typography.swift | 50 ++++--------------- .../Typography/UITextView+Typography.swift | 21 ++------ 3 files changed, 23 insertions(+), 90 deletions(-) diff --git a/SOOUM/SOOUM/Utilities/Typography/UILabel+Typography.swift b/SOOUM/SOOUM/Utilities/Typography/UILabel+Typography.swift index 29a03d83..798f0e9b 100644 --- a/SOOUM/SOOUM/Utilities/Typography/UILabel+Typography.swift +++ b/SOOUM/SOOUM/Utilities/Typography/UILabel+Typography.swift @@ -10,40 +10,16 @@ import UIKit /// https://github.com/Geri-Borbas/iOS.Blog.UILabel_Typography_Extensions extension UILabel { - - fileprivate struct Keys { - static var kUILabelTypography: String = "kUILabelTypography" - static var kUILabelTextObserver: String = "kUILabelTextObserver" - - static func setObjctForTypo(_ typography: Typography) { - withUnsafePointer(to: Self.kUILabelTypography) { - objc_setAssociatedObject(self, $0, typography, .OBJC_ASSOCIATION_RETAIN) - } - } - static func getObjectForTypo() -> Typography? { - withUnsafePointer(to: Self.kUILabelTypography) { - objc_getAssociatedObject(self, $0) as? Typography - } - } - - static func setObjectForObserver(_ textObserver: TextObserver?) { - withUnsafePointer(to: Self.kUILabelTextObserver) { - objc_setAssociatedObject(self, $0, textObserver, .OBJC_ASSOCIATION_RETAIN) - } - } - static func getObjectForObserver() -> TextObserver? { - withUnsafePointer(to: Self.kUILabelTextObserver) { - objc_getAssociatedObject(self, $0) as? TextObserver - } - } - } + + private static var kUILabelTypography: UInt8 = 0 + private static var kUILabelTextObserver: UInt8 = 0 func setTypography( _ typography: Typography, with closure: ((NSMutableAttributedString) -> Void)? = nil ) { - Keys.setObjctForTypo(typography) + objc_setAssociatedObject(self, &Self.kUILabelTypography, typography, .OBJC_ASSOCIATION_RETAIN) self.font = typography.font @@ -81,7 +57,7 @@ extension UILabel { } } get { - return Keys.getObjectForTypo() + return objc_getAssociatedObject(self, &Self.kUILabelTypography) as? Typography } } } @@ -93,11 +69,11 @@ extension UILabel { typealias TextChangeAction = (_ oldValue: String?, _ newValue: String?) -> Void fileprivate var observer: TextObserver? { - get { - Keys.getObjectForObserver() - } set { - Keys.setObjectForObserver(newValue) + objc_setAssociatedObject(self, &Self.kUILabelTextObserver, newValue, .OBJC_ASSOCIATION_RETAIN) + } + get { + return objc_getAssociatedObject(self, &Self.kUILabelTextObserver) as? TextObserver } } diff --git a/SOOUM/SOOUM/Utilities/Typography/UITextField+Typography.swift b/SOOUM/SOOUM/Utilities/Typography/UITextField+Typography.swift index 8df5a65c..38c9798a 100644 --- a/SOOUM/SOOUM/Utilities/Typography/UITextField+Typography.swift +++ b/SOOUM/SOOUM/Utilities/Typography/UITextField+Typography.swift @@ -9,40 +9,15 @@ import UIKit extension UITextField { - fileprivate struct Keys { - static var UITextFieldTypography: String = "UITextFieldTypography" - static var kUITextFieldConstraint: String = "kUITextFieldConstraint" - - static func setObjctForTypo(_ typography: Typography) { - withUnsafePointer(to: Self.UITextFieldTypography) { - objc_setAssociatedObject(self, $0, typography, .OBJC_ASSOCIATION_RETAIN) - } - } - static func getObjectForTypo() -> Typography? { - withUnsafePointer(to: Self.UITextFieldTypography) { - objc_getAssociatedObject(self, $0) as? Typography - } - } - - static func setObjectForConstraint(_ constraint: NSLayoutConstraint?) { - withUnsafePointer(to: Self.kUITextFieldConstraint) { - objc_setAssociatedObject(self, $0, constraint, .OBJC_ASSOCIATION_RETAIN) - } - } - - static func getObjectForConstraint() -> NSLayoutConstraint? { - withUnsafePointer(to: Self.kUITextFieldConstraint) { - objc_getAssociatedObject(self, $0) as? NSLayoutConstraint - } - } - } + private static var KUITextFieldTypography: UInt8 = 0 + private static var kUITextFieldConstraint: UInt8 = 0 func setTypography( _ typography: Typography, with closure: ((inout [NSAttributedString.Key: Any]) -> Void)? = nil ) { - Keys.setObjctForTypo(typography) + objc_setAssociatedObject(self, &Self.KUITextFieldTypography, typography, .OBJC_ASSOCIATION_RETAIN) if let constraint = self.constraint { constraint.constant = typography.lineHeight @@ -70,21 +45,16 @@ extension UITextField { } } get { - return Keys.getObjectForTypo() + return objc_getAssociatedObject(self, &Self.KUITextFieldTypography) as? Typography } } -} - -extension UITextField { - - static var kUITextFieldConstraint: String = "kUITextFieldConstraint" - - fileprivate var constraint: NSLayoutConstraint? { - get { - return Keys.getObjectForConstraint() - } + + private var constraint: NSLayoutConstraint? { set { - Keys.setObjectForConstraint(newValue) + objc_setAssociatedObject(self, &Self.kUITextFieldConstraint, newValue, .OBJC_ASSOCIATION_RETAIN) + } + get { + return objc_getAssociatedObject(self, &Self.kUITextFieldConstraint) as? NSLayoutConstraint } } } diff --git a/SOOUM/SOOUM/Utilities/Typography/UITextView+Typography.swift b/SOOUM/SOOUM/Utilities/Typography/UITextView+Typography.swift index 03a441d9..92f01ef7 100644 --- a/SOOUM/SOOUM/Utilities/Typography/UITextView+Typography.swift +++ b/SOOUM/SOOUM/Utilities/Typography/UITextView+Typography.swift @@ -8,28 +8,15 @@ import UIKit extension UITextView { - - fileprivate struct Keys { - static var kUITextViewTypography: String = "kUITextViewTypography" - - static func setObjctForTypo(_ typography: Typography) { - withUnsafePointer(to: Self.kUITextViewTypography) { - objc_setAssociatedObject(self, $0, typography, .OBJC_ASSOCIATION_RETAIN) - } - } - static func getObjectForTypo() -> Typography? { - withUnsafePointer(to: Self.kUITextViewTypography) { - objc_getAssociatedObject(self, $0) as? Typography - } - } - } + + private static var kUITextViewTypography: UInt8 = 0 func setTypography( _ typography: Typography, with closure: ((inout [NSAttributedString.Key: Any]) -> Void)? = nil ) { - Keys.setObjctForTypo(typography) + objc_setAssociatedObject(self, &Self.kUITextViewTypography, typography, .OBJC_ASSOCIATION_RETAIN) var attributes: [NSAttributedString.Key: Any] = typography.attributes attributes[.font] = typography.font @@ -54,7 +41,7 @@ extension UITextView { } } get { - return Keys.getObjectForTypo() + return objc_getAssociatedObject(self, &Self.kUITextViewTypography) as? Typography } } } From a8e4f4c51cdb01ad50502e01e8724d7c20627bb6 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Wed, 5 Nov 2025 18:03:35 +0900 Subject: [PATCH 18/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20placeholder=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=91=9C=EC=8B=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Home/Cells/HomePlaceholderViewCell.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Cells/HomePlaceholderViewCell.swift b/SOOUM/SOOUM/Presentations/Main/Home/Cells/HomePlaceholderViewCell.swift index 7fbfbc27..b77a3bf4 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Cells/HomePlaceholderViewCell.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Cells/HomePlaceholderViewCell.swift @@ -24,13 +24,13 @@ class HomePlaceholderViewCell: UITableViewCell { private let placeholderImageView = UIImageView().then { $0.image = .init(.image(.v2(.placeholder_home))) + $0.contentMode = .scaleAspectFit } private let placeholderMessageLabel = UILabel().then { $0.text = Text.message $0.textColor = .som.v2.gray400 $0.typography = .som.v2.body1 - $0.textAlignment = .center } @@ -57,15 +57,15 @@ class HomePlaceholderViewCell: UITableViewCell { self.contentView.addSubview(self.placeholderImageView) self.placeholderImageView.snp.makeConstraints { - let offset = UIScreen.main.bounds.height * 0.2 + let offset = UIScreen.main.bounds.height * 0.1 $0.top.equalToSuperview().offset(offset) $0.centerX.equalToSuperview() + $0.height.equalTo(113) } self.contentView.addSubview(self.placeholderMessageLabel) self.placeholderMessageLabel.snp.makeConstraints { $0.top.equalTo(self.placeholderImageView.snp.bottom).offset(20) - $0.bottom.equalToSuperview() $0.centerX.equalToSuperview() } } From 76a9c0d9011e616c6431287f004984097cc43ad4 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Wed, 5 Nov 2025 18:52:17 +0900 Subject: [PATCH 19/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20SOMCard=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DesignSystem/Components/SOMCard.swift | 171 +++++++----------- .../Detail/Cells/DetailViewFooterCell.swift | 2 +- 2 files changed, 71 insertions(+), 102 deletions(-) diff --git a/SOOUM/SOOUM/DesignSystem/Components/SOMCard.swift b/SOOUM/SOOUM/DesignSystem/Components/SOMCard.swift index cef88eec..e605b540 100644 --- a/SOOUM/SOOUM/DesignSystem/Components/SOMCard.swift +++ b/SOOUM/SOOUM/DesignSystem/Components/SOMCard.swift @@ -19,16 +19,21 @@ class SOMCard: UIView { static let pungedCardText: String = "카드가 삭제되었어요" } + enum CardType { + case feed + case comment + } + // MARK: Views - let shadowbackgroundView = UIView().then { + private let shadowbackgroundView = UIView().then { $0.backgroundColor = .som.v2.white $0.layer.cornerRadius = 16 } /// 배경 이미지 - let rootContainerImageView = UIImageView().then { + private let rootContainerImageView = UIImageView().then { $0.layer.cornerRadius = 16 $0.layer.borderWidth = 1 $0.contentMode = .scaleAspectFill @@ -36,160 +41,139 @@ class SOMCard: UIView { } // 본문 dim 배경 - let cardTextBackgroundBlurView = UIView().then { + private let cardTextBackgroundBlurView = UIView().then { $0.backgroundColor = .som.v2.dim $0.layer.cornerRadius = 12 $0.clipsToBounds = true } /// 본문 표시 라벨 (스크롤 X) - let cardTextContentLabel = UILabel().then { + private let cardTextContentLabel = UILabel().then { $0.textColor = .som.v2.white $0.typography = .som.v2.body1 $0.textAlignment = .center - $0.numberOfLines = 3 + $0.numberOfLines = 4 $0.lineBreakMode = .byTruncatingTail $0.lineBreakStrategy = .hangulWordPriority } - /// 본문 스크롤 텍스트 뷰 (스크롤 O) - let cardTextContentScrollView = UITextView().then { - $0.textColor = .som.v2.white - $0.typography = .som.v2.body1 - - $0.backgroundColor = .clear - $0.tintColor = .clear - - $0.textAlignment = .center - $0.textContainerInset = .init(top: 0, left: 16, bottom: 0, right: 16) - $0.textContainer.lineFragmentPadding = 0 - - $0.indicatorStyle = .white - $0.scrollIndicatorInsets = .init(top: 14, left: 0, bottom: 14, right: 0) - - $0.isScrollEnabled = false - $0.showsVerticalScrollIndicator = true - $0.showsHorizontalScrollIndicator = false - - $0.isEditable = false - } /// 펑 시간, 거리, 시간, 좋아요 수, 답글 수 정보를 담는 뷰 - let cardInfoContainer = UIView().then { + private let cardInfoContainer = UIView().then { $0.backgroundColor = .som.v2.white $0.layer.borderColor = UIColor.som.v2.white.cgColor $0.layer.borderWidth = 1 } /// 펑 시간, 거리, 시간을 담는 스택 뷰 - let cardInfoLeadingStackView = UIStackView().then { + private let cardInfoLeadingStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 4 $0.alignment = .center } /// 좋아요 수, 답글 수를 담는 스택 뷰 - let cardInfoTrailingStackView = UIStackView().then { + private let cardInfoTrailingStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 4 $0.alignment = .center } /// 어드민 정보 표시 스택뷰 - let adminStackView = UIStackView().then { + private let adminStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 2 $0.alignment = .center } /// 어드민 정보 아이콘 - let adminImageView = UIImageView().then { + private let adminImageView = UIImageView().then { $0.image = .init(.icon(.v2(.filled(.official)))) $0.tintColor = .som.v2.black } /// 어드민 정보 라벨 - let adminLabel = UILabel().then { + private let adminLabel = UILabel().then { $0.text = Text.adminTitle $0.textColor = .som.v2.black $0.typography = .som.v2.caption2 } /// 어드민 닷 - let firstDot = UIView().then { + private let firstDot = UIView().then { $0.backgroundColor = .som.v2.gray500 $0.layer.cornerRadius = 1 } /// 펑 남은시간 표시 스택뷰 - let cardPungTimeStackView = UIStackView().then { + private let cardPungTimeStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 2 $0.alignment = .center } /// 펑 남은시간 표시 아이콘 - let cardPungTimeImageView = UIImageView().then { + private let cardPungTimeImageView = UIImageView().then { $0.image = .init(.icon(.v2(.filled(.bomb)))) $0.tintColor = .som.v2.pMain } /// 펑 남은시간 표시 라벨 - let cardPungTimeLabel = UILabel().then { + private let cardPungTimeLabel = UILabel().then { $0.textColor = .som.v2.pDark $0.typography = .som.v2.caption2 } /// 펑 남은시간 닷 - let secondDot = UIView().then { + private let secondDot = UIView().then { $0.backgroundColor = .som.v2.gray500 $0.layer.cornerRadius = 1 } /// 거리 정보 표시 스택뷰 - let distanceInfoStackView = UIStackView().then { + private let distanceInfoStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 2 $0.alignment = .center } /// 거리 정보 아이콘 - let distanceImageView = UIImageView().then { + private let distanceImageView = UIImageView().then { $0.image = .init(.icon(.v2(.outlined(.location)))) $0.tintColor = .som.v2.gray500 } /// 거리 정보 라벨 - let distanceLabel = UILabel().then { + private let distanceLabel = UILabel().then { $0.textColor = .som.v2.gray500 $0.typography = .som.v2.caption2 } /// 거리 정보 닷 - let thirdDot = UIView().then { + private let thirdDot = UIView().then { $0.backgroundColor = .som.v2.gray500 $0.layer.cornerRadius = 1 } /// 시간 정보 표시 라벨 - let timeLabel = UILabel().then { + private let timeLabel = UILabel().then { $0.textColor = .som.v2.gray500 $0.typography = .som.v2.caption2 } /// 좋아요 정보 표시 스택뷰 - let likeInfoStackView = UIStackView().then { + private let likeInfoStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 2 $0.alignment = .center } /// 좋아요 정보 표시 아이콘 - let likeImageView = UIImageView().then { + private let likeImageView = UIImageView().then { $0.image = .init(.icon(.v2(.outlined(.heart)))) $0.tintColor = .som.v2.gray500 } /// 좋아요 정보 표시 라벨 - let likeLabel = UILabel().then { + private let likeLabel = UILabel().then { $0.textColor = .som.v2.gray500 $0.typography = .som.v2.caption2 } /// 답카드 정보 표시 스택뷰 - let commentInfoStackView = UIStackView().then { + private let commentInfoStackView = UIStackView().then { $0.axis = .horizontal $0.spacing = 2 $0.alignment = .center } /// 답카드 정보 표시 아이콘 - let commentImageView = UIImageView().then { + private let commentImageView = UIImageView().then { $0.image = .init(.icon(.v2(.outlined(.message_circle)))) $0.tintColor = .som.v2.gray500 } /// 답카드 정보 표시 라벨 - let commentLabel = UILabel().then { + private let commentLabel = UILabel().then { $0.textColor = .som.v2.gray500 $0.typography = .som.v2.caption2 } @@ -197,16 +181,14 @@ class SOMCard: UIView { // MARK: Variables - var model: BaseCardInfo? - - private var hasScrollEnabled: Bool + private(set) var model: BaseCardInfo = .defaultValue + private(set) var cardType: CardType // MARK: Constraints - // TODO: 카드 본문 배경 블러 뷰 높이 계산 Constraint, 헌재 사용 X + // TODO: 카드 본문 높이 계산 Constraint private var contentHeightConstraint: Constraint? - private var scrollContentHieghtConstraint: Constraint? /// 펑 이벤트 처리 위해 추가 var serialTimer: Disposable? @@ -215,8 +197,8 @@ class SOMCard: UIView { // MARK: Initialize - init(hasScrollEnabled: Bool = false) { - self.hasScrollEnabled = hasScrollEnabled + init(type cardType: CardType = .feed) { + self.cardType = cardType super.init(frame: .zero) self.setupConstraints() @@ -342,22 +324,14 @@ class SOMCard: UIView { $0.trailing.equalToSuperview().offset(-32) } - if self.hasScrollEnabled { - self.cardTextBackgroundBlurView.addSubview(self.cardTextContentScrollView) - self.cardTextContentScrollView.snp.makeConstraints { - $0.top.equalToSuperview().offset(20) - $0.bottom.equalToSuperview().offset(-20) - $0.leading.equalToSuperview().offset(24) - $0.trailing.equalToSuperview().offset(-24) - } - } else { - self.cardTextBackgroundBlurView.addSubview(self.cardTextContentLabel) - self.cardTextContentLabel.snp.makeConstraints { - $0.top.equalToSuperview().offset(20) - $0.bottom.equalToSuperview().offset(-20) - $0.leading.equalToSuperview().offset(24) - $0.trailing.equalToSuperview().offset(-24) - } + self.cardTextBackgroundBlurView.addSubview(self.cardTextContentLabel) + self.cardTextContentLabel.snp.makeConstraints { + let verticalOffset: CGFloat = self.cardType == .feed ? 20 : 16 + $0.top.equalToSuperview().offset(verticalOffset) + $0.bottom.equalToSuperview().offset(-verticalOffset) + $0.leading.equalToSuperview().offset(24) + $0.trailing.equalToSuperview().offset(-24) + self.contentHeightConstraint = $0.height.equalTo(Typography.som.v2.body1.lineHeight).constraint } } @@ -368,6 +342,13 @@ class SOMCard: UIView { func prepareForReuse() { self.serialTimer?.dispose() self.disposeBag = DisposeBag() + + self.adminLabel.text = nil + self.cardPungTimeLabel.text = nil + self.distanceLabel.text = nil + self.timeLabel.text = nil + self.likeLabel.text = nil + self.commentLabel.text = nil } /// 홈피드 모델 초기화 @@ -386,13 +367,9 @@ class SOMCard: UIView { case .yoonwoo: typography = .som.v2.yoonwooCard case .kkookkkook: typography = .som.v2.kkookkkookCard } - if self.hasScrollEnabled { - self.cardTextContentScrollView.text = model.cardContent - self.cardTextContentScrollView.typography = typography - } else { - self.cardTextContentLabel.text = model.cardContent - self.cardTextContentLabel.typography = typography - } + self.cardTextContentLabel.text = model.cardContent + self.cardTextContentLabel.typography = typography + self.updateContentHeight(model.cardContent, with: typography) // 하단 정보 // 어드민, 펑 시간, 거리, 시간 @@ -461,39 +438,35 @@ class SOMCard: UIView { } // TODO: 카드 본문 배경 블러 뷰 높이 계산 함수, 헌재 사용 X - private func updateContentHeight(_ text: String) { + private func updateContentHeight(_ text: String, with typography: Typography) { - self.layoutIfNeeded() - // TODO: 임시, 폰트 가변임 - let typography = Typography.som.v2.body1 + UIView.performWithoutAnimation { + self.layoutIfNeeded() + } + var attributes = typography.attributes attributes.updateValue(typography.font, forKey: .font) let attributedText = NSAttributedString( string: text, attributes: attributes ) - - let availableWidth = UIScreen.main.bounds.width - 16 * 2 - 32 * 2 - 24 * 2 + /// screen width - SOMCard horizontal padding - text background dim view horizontal padding - text horizontal inset + let availableWidth = self.cardTextContentLabel.bounds.width let size: CGSize = .init(width: availableWidth, height: .greatestFiniteMagnitude) - let boundingRect = attributedText.boundingRect( + let boundingHeight = attributedText.boundingRect( with: size, options: [.usesLineFragmentOrigin], context: nil - ) - let boundingHeight = boundingRect.height + 20 * 2 /// top, bottom inset - let backgroundHeight = rootContainerImageView.bounds.height + ).height + let backgroundHeight = self.rootContainerImageView.bounds.height - let height = min(boundingHeight, (backgroundHeight - 34) * 0.8) + let maxHeight = self.cardType == .feed ? typography.lineHeight * 3 : typography.lineHeight * 4 + let height = min(boundingHeight, maxHeight) self.contentHeightConstraint?.update(offset: height) - if self.hasScrollEnabled { - self.cardTextContentScrollView.isScrollEnabled = boundingHeight > backgroundHeight * 0.5 - self.cardTextContentScrollView.isUserInteractionEnabled = true - self.cardTextContentScrollView.contentSize = .init( - width: cardTextContentScrollView.bounds.width, - height: boundingHeight - ) + UIView.performWithoutAnimation { + self.layoutIfNeeded() } } @@ -536,10 +509,6 @@ class SOMCard: UIView { .filter { $0 != self.cardPungTimeStackView } .forEach { $0.removeFromSuperview() } - if self.hasScrollEnabled { - self.cardTextContentScrollView.text = Text.pungedCardText - } else { - self.cardTextContentLabel.text = Text.pungedCardText - } + self.cardTextContentLabel.text = Text.pungedCardText } } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewFooterCell.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewFooterCell.swift index 6636423d..cb916734 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewFooterCell.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewFooterCell.swift @@ -17,7 +17,7 @@ class DetailViewFooterCell: UICollectionViewCell { // MARK: Views - private let cardView = SOMCard() + private let cardView = SOMCard(type: .comment) // MARK: Initialize From 24e04ae642ed932b933d45337319fd3f64c2581f Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Wed, 5 Nov 2025 18:54:53 +0900 Subject: [PATCH 20/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EC=9E=91=EC=84=B1=EB=90=9C=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EA=B0=84=EA=B2=A9=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Home/Detail/Views/WrittenTags/WrittenTags.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/WrittenTags/WrittenTags.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/WrittenTags/WrittenTags.swift index 479ef32a..c9f26e11 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/WrittenTags/WrittenTags.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/WrittenTags/WrittenTags.swift @@ -30,7 +30,7 @@ class WrittenTags: UIView { collectionViewLayout: UICollectionViewFlowLayout().then { $0.scrollDirection = .horizontal $0.minimumInteritemSpacing = 6 - $0.minimumLineSpacing = 0 + $0.minimumLineSpacing = 6 } ).then { $0.backgroundColor = .clear From 8582e4d89c52b1cab89ee9841961f59d4160e7ca Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Wed, 5 Nov 2025 18:56:27 +0900 Subject: [PATCH 21/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=ED=95=98=EB=8B=A8?= =?UTF-8?q?=20=ED=83=AD=EB=B0=94=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/SOMTabBarController/SOMTabBarItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift b/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift index fc3e0d44..e4b70886 100644 --- a/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift +++ b/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift @@ -17,7 +17,7 @@ class SOMTabBarItem: UIView { // MARK: Views private let imageView = UIImageView().then { - $0.tintColor = .som.gray400 + $0.tintColor = .som.gray300 } private let titleLabel = UILabel().then { From 20b7e4ef14582ce7391e9645066cc0a2f27d0eb7 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 02:08:24 +0900 Subject: [PATCH 22/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20rpeonse=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM.xcodeproj/project.pbxproj | 12 +++++++ .../Models/Responses/WriteCardResponse.swift | 32 +++++++++++++++++++ .../Repositories/CardRepositoryImpl.swift | 4 +-- .../Remotes/CardRemoteDataSourceImpl.swift | 8 ++--- .../Interfaces/CardRemoteDataSource.swift | 4 +-- .../Domain/Models/EntranceCardType.swift | 13 ++++++++ .../Domain/Repositories/CardRepository.swift | 4 +-- .../Domain/UseCases/CardUseCaseImpl.swift | 8 ++--- .../UseCases/Interfaces/CardUseCase.swift | 4 +-- 9 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 SOOUM/SOOUM/Data/Models/Responses/WriteCardResponse.swift create mode 100644 SOOUM/SOOUM/Domain/Models/EntranceCardType.swift diff --git a/SOOUM/SOOUM.xcodeproj/project.pbxproj b/SOOUM/SOOUM.xcodeproj/project.pbxproj index e4ff05d7..3e5e76a0 100644 --- a/SOOUM/SOOUM.xcodeproj/project.pbxproj +++ b/SOOUM/SOOUM.xcodeproj/project.pbxproj @@ -638,6 +638,10 @@ 38D2FBCF2E81B52F006DD739 /* SOMSwipableTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D2FBCD2E81B529006DD739 /* SOMSwipableTabBar.swift */; }; 38D2FBD12E81B9B7006DD739 /* SOMSwipableTabBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D2FBD02E81B9B0006DD739 /* SOMSwipableTabBarDelegate.swift */; }; 38D2FBD22E81B9B7006DD739 /* SOMSwipableTabBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D2FBD02E81B9B0006DD739 /* SOMSwipableTabBarDelegate.swift */; }; + 38D478072EBBAA0B0041FF6C /* WriteCardResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D478062EBBAA080041FF6C /* WriteCardResponse.swift */; }; + 38D478082EBBAA0B0041FF6C /* WriteCardResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D478062EBBAA080041FF6C /* WriteCardResponse.swift */; }; + 38D4780A2EBBABF60041FF6C /* EntranceCardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D478092EBBABE40041FF6C /* EntranceCardType.swift */; }; + 38D4780B2EBBABF60041FF6C /* EntranceCardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D478092EBBABE40041FF6C /* EntranceCardType.swift */; }; 38D488CA2D0C557300F2D38D /* SOMButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D488C92D0C557300F2D38D /* SOMButton.swift */; }; 38D488CB2D0C557300F2D38D /* SOMButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D488C92D0C557300F2D38D /* SOMButton.swift */; }; 38D522682E742F610044911B /* SOMLoadingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D522672E742F550044911B /* SOMLoadingIndicatorView.swift */; }; @@ -1095,6 +1099,8 @@ 38D2FBCA2E81B0DE006DD739 /* SOMSwipableTabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOMSwipableTabBarItem.swift; sourceTree = ""; }; 38D2FBCD2E81B529006DD739 /* SOMSwipableTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOMSwipableTabBar.swift; sourceTree = ""; }; 38D2FBD02E81B9B0006DD739 /* SOMSwipableTabBarDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOMSwipableTabBarDelegate.swift; sourceTree = ""; }; + 38D478062EBBAA080041FF6C /* WriteCardResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteCardResponse.swift; sourceTree = ""; }; + 38D478092EBBABE40041FF6C /* EntranceCardType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntranceCardType.swift; sourceTree = ""; }; 38D488C92D0C557300F2D38D /* SOMButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOMButton.swift; sourceTree = ""; }; 38D522672E742F550044911B /* SOMLoadingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOMLoadingIndicatorView.swift; sourceTree = ""; }; 38D5637A2D16D72D006265AA /* SOMStickyTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOMStickyTabBar.swift; sourceTree = ""; }; @@ -2170,6 +2176,7 @@ 38899E632E7938CD0030F7CA /* Responses */ = { isa = PBXGroup; children = ( + 38D478062EBBAA080041FF6C /* WriteCardResponse.swift */, 38E928BE2EB72D3600B3F00B /* DetailCardInfoResponse.swift */, 38EBA9102EB3999C008B28F4 /* PostingPermissionResponse.swift */, 38C9AF0D2E96601E00B401C0 /* DefaultImagesResponse.swift */, @@ -2207,6 +2214,7 @@ 38899E692E793AEA0030F7CA /* Models */ = { isa = PBXGroup; children = ( + 38D478092EBBABE40041FF6C /* EntranceCardType.swift */, 38E928B82EB715C300B3F00B /* ReortType.swift */, 38E928B52EB711DE00B3F00B /* DetailCardInfo.swift */, 38EBA90D2EB39917008B28F4 /* PostingPermission.swift */, @@ -3253,6 +3261,7 @@ 388D8AE02E73E6190044BA79 /* SwiftEntryKit.swift in Sources */, 38899E962E7953310030F7CA /* NotificationInfoResponse.swift in Sources */, 38FEBE5F2E86612C002916A8 /* NoticeViewCell.swift in Sources */, + 38D4780A2EBBABF60041FF6C /* EntranceCardType.swift in Sources */, 2AFD055A2D008D23007C84AD /* TagDetailViewController.swift in Sources */, 2AFF95562CF3222400CBFB12 /* TagsViewController.swift in Sources */, 38C9AF182E96693600B401C0 /* TagRemoteDataSource.swift in Sources */, @@ -3311,6 +3320,7 @@ 3878D0862CFFED7800F9522F /* TermsOfServiceTextCellView.swift in Sources */, 38899E832E794C360030F7CA /* LoginResponse.swift in Sources */, 2ACBD41B2CCA03790057C013 /* ImageURLWithName.swift in Sources */, + 38D478072EBBAA0B0041FF6C /* WriteCardResponse.swift in Sources */, 38E928C02EB72D3D00B3F00B /* DetailCardInfoResponse.swift in Sources */, 38B6AAE02CA4777200CE6DB6 /* UIViewController+Rx.swift in Sources */, 2ACBD41E2CCAB3490057C013 /* PresignedStorageResponse.swift in Sources */, @@ -3634,6 +3644,7 @@ 38FEBE5E2E86612C002916A8 /* NoticeViewCell.swift in Sources */, 38F3D9302D06C2370049F575 /* SOMAnimationTransitioning.swift in Sources */, 38C9AF172E96693600B401C0 /* TagRemoteDataSource.swift in Sources */, + 38D4780B2EBBABF60041FF6C /* EntranceCardType.swift in Sources */, 385602B62D2FB18400118530 /* NotificationPlaceholderViewCell.swift in Sources */, 38E9CE192D37FED000E85A2D /* AddingTokenInterceptor.swift in Sources */, 380F42242E884AE5009AC59E /* CardRemoteDataSource.swift in Sources */, @@ -3692,6 +3703,7 @@ 2AFF955D2CF328DE00CBFB12 /* FavoriteTagTableViewCell.swift in Sources */, 38899E842E794C360030F7CA /* LoginResponse.swift in Sources */, 385053522C92DBE200C80B02 /* SOMTabBarItem.swift in Sources */, + 38D478082EBBAA0B0041FF6C /* WriteCardResponse.swift in Sources */, 38E928BF2EB72D3D00B3F00B /* DetailCardInfoResponse.swift in Sources */, 38A5D1542C8CB11E00B68363 /* UIImage+SOOUM.swift in Sources */, 38601E182D31399400A465A9 /* CardRequest.swift in Sources */, diff --git a/SOOUM/SOOUM/Data/Models/Responses/WriteCardResponse.swift b/SOOUM/SOOUM/Data/Models/Responses/WriteCardResponse.swift new file mode 100644 index 00000000..6d199afa --- /dev/null +++ b/SOOUM/SOOUM/Data/Models/Responses/WriteCardResponse.swift @@ -0,0 +1,32 @@ +// +// WriteCardResponse.swift +// SOOUM +// +// Created by 오현식 on 11/6/25. +// + +import Alamofire + +struct WriteCardResponse { + + let cardId: String +} + +extension WriteCardResponse: EmptyResponse { + + static func emptyValue() -> WriteCardResponse { + WriteCardResponse(cardId: "") + } +} + +extension WriteCardResponse: Decodable { + + enum CodingKeys: CodingKey { + case cardId + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.cardId = String(try container.decode(Int64.self, forKey: .cardId)) + } +} diff --git a/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift b/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift index be81750a..12f950ac 100644 --- a/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift +++ b/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift @@ -86,7 +86,7 @@ class CardRepositoryImpl: CardRepository { imgName: String, isStory: Bool, tags: [String] - ) -> Observable { + ) -> Observable { return self.remoteDataSource.writeCard( isDistanceShared: isDistanceShared, @@ -111,7 +111,7 @@ class CardRepositoryImpl: CardRepository { imgType: String, imgName: String, tags: [String] - ) -> Observable { + ) -> Observable { return self.remoteDataSource.writeComment( id: id, diff --git a/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift b/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift index b6eeb03c..a14d00c7 100644 --- a/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift +++ b/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift @@ -96,7 +96,7 @@ class CardRemoteDataSourceImpl: CardRemoteDataSource { imgName: String, isStory: Bool, tags: [String] - ) -> Observable { + ) -> Observable { let request: CardRequest = .writeCard( isDistanceShared: isDistanceShared, @@ -109,7 +109,7 @@ class CardRemoteDataSourceImpl: CardRemoteDataSource { isStory: isStory, tags: tags ) - return self.provider.networkManager.perform(request) + return self.provider.networkManager.perform(WriteCardResponse.self, request: request) } func writeComment( @@ -122,7 +122,7 @@ class CardRemoteDataSourceImpl: CardRemoteDataSource { imgType: String, imgName: String, tags: [String] - ) -> Observable { + ) -> Observable { let request: CardRequest = .writeComment( id: id, @@ -135,6 +135,6 @@ class CardRemoteDataSourceImpl: CardRemoteDataSource { imgName: imgName, tags: tags ) - return self.provider.networkManager.perform(request) + return self.provider.networkManager.perform(WriteCardResponse.self, request: request) } } diff --git a/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift b/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift index e2969043..4f6a5547 100644 --- a/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift +++ b/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift @@ -42,7 +42,7 @@ protocol CardRemoteDataSource { imgName: String, isStory: Bool, tags: [String] - ) -> Observable + ) -> Observable func writeComment( id: String, isDistanceShared: Bool, @@ -53,5 +53,5 @@ protocol CardRemoteDataSource { imgType: String, imgName: String, tags: [String] - ) -> Observable + ) -> Observable } diff --git a/SOOUM/SOOUM/Domain/Models/EntranceCardType.swift b/SOOUM/SOOUM/Domain/Models/EntranceCardType.swift new file mode 100644 index 00000000..f825c12d --- /dev/null +++ b/SOOUM/SOOUM/Domain/Models/EntranceCardType.swift @@ -0,0 +1,13 @@ +// +// EntranceCardType.swift +// SOOUM +// +// Created by 오현식 on 11/6/25. +// + +import Foundation + +enum EntranceCardType { + case feed + case comment +} diff --git a/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift b/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift index 27d6a910..2c8a5dbc 100644 --- a/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift +++ b/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift @@ -42,7 +42,7 @@ protocol CardRepository { imgName: String, isStory: Bool, tags: [String] - ) -> Observable + ) -> Observable func writeComment( id: String, isDistanceShared: Bool, @@ -53,5 +53,5 @@ protocol CardRepository { imgType: String, imgName: String, tags: [String] - ) -> Observable + ) -> Observable } diff --git a/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift b/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift index 7cfa138e..a8906118 100644 --- a/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift +++ b/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift @@ -86,7 +86,7 @@ class CardUseCaseImpl: CardUseCase { imgName: String, isStory: Bool, tags: [String] - ) -> Observable { + ) -> Observable { return self.repository.writeCard( isDistanceShared: isDistanceShared, @@ -99,7 +99,7 @@ class CardUseCaseImpl: CardUseCase { isStory: isStory, tags: tags ) - .map { $0 == 200 } + .map { $0.cardId } } func writeComment( @@ -112,7 +112,7 @@ class CardUseCaseImpl: CardUseCase { imgType: String, imgName: String, tags: [String] - ) -> Observable { + ) -> Observable { return self.repository.writeComment( id: id, @@ -125,6 +125,6 @@ class CardUseCaseImpl: CardUseCase { imgName: imgName, tags: tags ) - .map { $0 == 200 } + .map { $0.cardId } } } diff --git a/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift b/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift index ebe0374e..740a92ff 100644 --- a/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift +++ b/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift @@ -42,7 +42,7 @@ protocol CardUseCase { imgName: String, isStory: Bool, tags: [String] - ) -> Observable + ) -> Observable func writeComment( id: String, isDistanceShared: Bool, @@ -53,5 +53,5 @@ protocol CardUseCase { imgType: String, imgName: String, tags: [String] - ) -> Observable + ) -> Observable } From 6d35ef63947572abb3d421c329313f8e04f813e5 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 02:09:11 +0900 Subject: [PATCH 23/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=20=EC=B9=B4=EB=93=9C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/MainTabBarReactor.swift | 2 +- .../Main/Write/WriteCardViewController.swift | 32 ++++++-- .../Main/Write/WriteCardViewReactor.swift | 81 ++++++------------- 3 files changed, 49 insertions(+), 66 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/MainTabBarReactor.swift b/SOOUM/SOOUM/Presentations/Main/MainTabBarReactor.swift index ae98b23b..c6619121 100644 --- a/SOOUM/SOOUM/Presentations/Main/MainTabBarReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/MainTabBarReactor.swift @@ -114,7 +114,7 @@ extension MainTabBarReactor { } func reactorForWriteCard() -> WriteCardViewReactor { - WriteCardViewReactor(dependencies: self.dependencies, type: .card) + WriteCardViewReactor(dependencies: self.dependencies) } // func reactorForTags() -> TagsViewReactor { diff --git a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift index 796db663..63d97985 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift @@ -124,7 +124,7 @@ class WriteCardViewController: BaseNavigationViewController, View { override func setupNaviBar() { super.setupNaviBar() - self.navigationBar.title = self.reactor?.requestType == .card ? Text.navigationTitle : Text.commentNavigationTitle + self.navigationBar.title = self.reactor?.entranceType == .feed ? Text.navigationTitle : Text.commentNavigationTitle self.navigationBar.setRightButtons([self.writeButton]) } @@ -182,7 +182,7 @@ class WriteCardViewController: BaseNavigationViewController, View { func bind(reactor: WriteCardViewReactor) { var options: [SelectOptionItem.OptionType] { - if reactor.requestType == .card { + if reactor.entranceType == .feed { return [.distanceShare, .story] } else { return [.distanceShare] @@ -429,6 +429,7 @@ class WriteCardViewController: BaseNavigationViewController, View { .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, isProcessing in object.view.endEditing(true) + if isProcessing { object.loadingIndicatorView.startAnimating() } else { @@ -437,14 +438,31 @@ class WriteCardViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) - reactor.state.map(\.isWritten) + reactor.state.map(\.writtenCardId) .filterNil() .distinctUntilChanged() - .filter { $0 } .observe(on: MainScheduler.asyncInstance) - .subscribe(with: self) { object, _ in - object.navigationPop { - NotificationCenter.default.post(name: .reloadData, object: object) + .subscribe(with: self) { object, writtenCardId in + NotificationCenter.default.post(name: .reloadData, object: nil, userInfo: nil) + if reactor.entranceType == .comment { + NotificationCenter.default.post(name: .reloadCommentsData, object: nil, userInfo: nil) + } + + if let navigationController = object.navigationController { + + let detailViewController = DetailViewController() + detailViewController.reactor = reactor.reactorForDetail(with: writtenCardId) + + var viewControllers = navigationController.viewControllers + if (viewControllers.popLast() as? Self) != nil { + + viewControllers.append(detailViewController) + navigationController.setViewControllers(viewControllers, animated: true) + } else { + object.navigationPop() + } + } else { + object.navigationPop() } } .disposed(by: self.disposeBag) diff --git a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift index e535eacc..211f1d80 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift @@ -10,11 +10,6 @@ import ReactorKit class WriteCardViewReactor: Reactor { - enum RequestType { - case card - case comment - } - enum Action: Equatable { case landing case updateUserImage(UIImage?, Bool) @@ -27,14 +22,6 @@ class WriteCardViewReactor: Reactor { isStory: Bool, tags: [String] ) - // case writeComment( - // isDistanceShared: Bool, - // content: String, - // font: String, - // imgType: String, - // imgName: String, - // commentTags: [String] - // ) case relatedTags(keyword: String) case updateRelatedTags } @@ -42,7 +29,7 @@ class WriteCardViewReactor: Reactor { enum Mutation { case defaultImages(DefaultImages) case updateUserImage(UIImage?, Bool) - case writeCard(Bool) + case writeCard(String?) case relatedTags([TagInfo]) case updateIsProcessing(Bool) case updateErrors(Int?) @@ -52,9 +39,9 @@ class WriteCardViewReactor: Reactor { fileprivate(set) var shouldUseCoordinates: Bool fileprivate(set) var defaultImages: DefaultImages? fileprivate(set) var userImage: UIImage? - fileprivate(set) var isDownloaded: Bool? - fileprivate(set) var isWritten: Bool? fileprivate(set) var relatedTags: [TagInfo]? + fileprivate(set) var writtenCardId: String? + fileprivate(set) var isDownloaded: Bool? fileprivate(set) var isProcessing: Bool fileprivate(set) var hasErrors: Int? } @@ -63,9 +50,9 @@ class WriteCardViewReactor: Reactor { shouldUseCoordinates: false, defaultImages: nil, userImage: nil, - isDownloaded: nil, - isWritten: nil, relatedTags: nil, + writtenCardId: nil, + isDownloaded: nil, isProcessing: false, hasErrors: nil ) @@ -77,26 +64,22 @@ class WriteCardViewReactor: Reactor { let locationManager: LocationManagerDelegate - let requestType: RequestType + let entranceType: EntranceCardType private let parentCardId: String? - // let parentPungTime: Date? init( dependencies: AppDIContainerable, - type requestType: RequestType, + type entranceType: EntranceCardType = .feed, parentCardId: String? = nil - // parentPungTime: Date? = nil ) { self.dependencies = dependencies self.cardUseCase = dependencies.rootContainer.resolve(CardUseCase.self) self.tagUseCase = dependencies.rootContainer.resolve(TagUseCase.self) self.userUseCase = dependencies.rootContainer.resolve(UserUseCase.self) self.locationManager = dependencies.rootContainer.resolve(ManagerProviderType.self).locationManager - self.requestType = requestType + self.entranceType = entranceType self.parentCardId = parentCardId - // self.parentCardId = parentCardId - // self.parentPungTime = parentPungTime } func mutate(action: Action) -> Observable { @@ -133,32 +116,6 @@ class WriteCardViewReactor: Reactor { .delay(.milliseconds(1000), scheduler: MainScheduler.instance), .just(.updateIsProcessing(false)) ]) - // case let .writeComment( - // isDistanceShared, - // content, - // font, - // imgType, - // imgName, - // commentTags - // ): - // let coordinate = self.provider.locationManager.coordinate - // let trimedContent = content.trimmingCharacters(in: .whitespacesAndNewlines) - // - // let request: CardRequest = .writeComment( - // id: self.parentCardId ?? "", - // isDistanceShared: !isDistanceShared, - // latitude: coordinate.latitude, - // longitude: coordinate.longitude, - // content: trimedContent, - // font: font, - // imgType: imgType, - // imgName: imgName, - // commentTags: commentTags - // ) - // - // return self.provider.networkManager.request(Status.self, request: request) - // .map { .writeCard($0.httpCode == 201) } - // .catch(self.catchClosure) case let .relatedTags(keyword): return self.tagUseCase.relatedTags(keyword: keyword, size: 8) @@ -177,8 +134,8 @@ class WriteCardViewReactor: Reactor { case let .updateUserImage(userImage, isDownloaded): newState.userImage = userImage newState.isDownloaded = isDownloaded - case let .writeCard(isWritten): - newState.isWritten = isWritten + case let .writeCard(writtenCardId): + newState.writtenCardId = writtenCardId case let .relatedTags(relatedTags): newState.relatedTags = relatedTags case let .updateIsProcessing(isProcessing): @@ -227,7 +184,7 @@ private extension WriteCardViewReactor { if case .default = imageType, let imageName = imageName { - if self.requestType == .card { + if self.entranceType == .feed { return self.cardUseCase.writeCard( isDistanceShared: isDistanceShared, @@ -263,9 +220,9 @@ private extension WriteCardViewReactor { return self.uploadImage(image) .withUnretained(self) .flatMapLatest { object, imageName -> Observable in - guard let imageName = imageName else { return .just(.writeCard(false)) } + guard let imageName = imageName else { return .just(.writeCard(nil)) } - if self.requestType == .card { + if self.entranceType == .feed { return object.cardUseCase.writeCard( isDistanceShared: isDistanceShared, @@ -297,7 +254,7 @@ private extension WriteCardViewReactor { } } - return .just(.writeCard(false)) + return .just(.writeCard(nil)) } var catchClosure: ((Error) throws -> Observable ) { @@ -305,10 +262,18 @@ private extension WriteCardViewReactor { let nsError = error as NSError return .concat([ - .just(.writeCard(false)), + .just(.writeCard(nil)), .just(.updateIsProcessing(false)), .just(.updateErrors(nsError.code)) ]) } } } + + +extension WriteCardViewReactor { + + func reactorForDetail(with targetCardId: String) -> DetailViewReactor { + DetailViewReactor(dependencies: self.dependencies, self.entranceType, type: .navi, with: targetCardId) + } +} From dc21448b4adba5bc84dbfc7e82e45672028a7f66 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 02:09:49 +0900 Subject: [PATCH 24/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Detail/DetailViewController.swift | 46 ++++++++----------- .../Main/Home/Detail/DetailViewReactor.swift | 21 +++++---- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index a809fc1a..221ff972 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -124,8 +124,8 @@ class DetailViewController: BaseNavigationViewController, View { NotificationCenter.default.addObserver( self, - selector: #selector(self.reloadData(_:)), - name: .reloadData, + selector: #selector(self.reloadCommentsData(_:)), + name: .reloadCommentsData, object: nil ) @@ -140,9 +140,13 @@ class DetailViewController: BaseNavigationViewController, View { override func setupNaviBar() { super.setupNaviBar() - self.navigationBar.title = self.reactor?.detailType == .feed ? Text.feedDetailNavigationTitle : Text.commentDetailNavigationTitle + guard let reactor = self.reactor else { return } - if self.reactor?.detailType == .comment { + self.navigationBar.title = reactor.entranceCardType == .feed ? + Text.feedDetailNavigationTitle : + Text.commentDetailNavigationTitle + + if reactor.entranceCardType == .comment { self.navigationBar.setLeftButtons([self.leftHomeButton]) } self.navigationBar.setRightButtons([self.rightMoreButton]) @@ -170,21 +174,6 @@ class DetailViewController: BaseNavigationViewController, View { } } - override func bind() { - super.bind() - - // Navigation pop to root - self.leftHomeButton.rx.throttleTap - .subscribe(with: self) { object, _ in - if let navigationController = object.navigationController { - navigationController.popToRootViewController(animated: false) - } else { - object.navigationPop(animated: false) - } - } - .disposed(by: self.disposeBag) - } - // MARK: - Bind @@ -282,11 +271,14 @@ class DetailViewController: BaseNavigationViewController, View { // 카드 삭제 후 X 버튼 액션 self.rightDeleteButton.rx.throttleTap .subscribe(with: self) { object, _ in - if let navigationController = object.navigationController { - navigationController.popToRootViewController(animated: false) - } else { - object.navigationPop(animated: false) - } + object.navigationPop(to: HomeViewController.self, animated: false) + } + .disposed(by: self.disposeBag) + + // 답카드 홈 버튼 액션 + self.leftHomeButton.rx.throttleTap + .subscribe(with: self) { object, _ in + object.navigationPop(to: HomeViewController.self, animated: false) } .disposed(by: self.disposeBag) @@ -392,6 +384,8 @@ class DetailViewController: BaseNavigationViewController, View { .filter { $0 } .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, _ in + NotificationCenter.default.post(name: .reloadData, object: nil, userInfo: nil) + object.navigationBar.title = Text.deletedNavigationTitle object.navigationBar.setRightButtons([object.rightDeleteButton]) @@ -433,9 +427,9 @@ class DetailViewController: BaseNavigationViewController, View { // MARK: Objc func @objc - private func reloadData(_ notification: Notification) { + private func reloadCommentsData(_ notification: Notification) { - self.reactor?.action.onNext(.landing) + self.reactor?.action.onNext(.refreshForComment) } @objc diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift index 7c88d986..c961bc83 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewReactor.swift @@ -15,13 +15,9 @@ class DetailViewReactor: Reactor { case navi } - enum DetailType { - case feed - case comment - } - enum Action: Equatable { case landing + case refreshForComment case refresh case moreFindForComment(lastId: String) case delete @@ -69,13 +65,13 @@ class DetailViewReactor: Reactor { private let locationManager: LocationManagerDelegate - let detailType: DetailType + let entranceCardType: EntranceCardType let entranceType: EntranceType let selectedCardId: String init( dependencies: AppDIContainerable, - _ detailType: DetailType, + _ entranceCardType: EntranceCardType, type entranceType: EntranceType = .navi, with selectedCardId: String ) { @@ -84,7 +80,7 @@ class DetailViewReactor: Reactor { self.locationManager = dependencies.rootContainer.resolve(ManagerProviderType.self).locationManager - self.detailType = detailType + self.entranceCardType = entranceCardType self.entranceType = entranceType self.selectedCardId = selectedCardId } @@ -94,14 +90,19 @@ class DetailViewReactor: Reactor { case .landing: return .concat([ - self.detailCard(), + self.detailCard() + .catch(self.catchClosure), self.commentCards() ]) + case .refreshForComment: + + return self.commentCards() case .refresh: return .concat([ .just(.updateIsRefreshing(true)), - self.detailCard(), + self.detailCard() + .catch(self.catchClosure), self.commentCards(), .just(.updateIsRefreshing(false)) ]) From dbee11c280684cbce052d2fed19773dabef619be Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 02:10:12 +0900 Subject: [PATCH 25/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Models/CommonNotificationInfo.swift | 2 +- .../Models/CompositeNotificationInfo.swift | 2 +- .../SOOUM/Extensions/Cocoa/Notification.swift | 1 + .../NotificationViewController.swift | 42 +++++++++---- .../NotificationViewReactor.swift | 60 ++++++++++--------- 5 files changed, 65 insertions(+), 42 deletions(-) diff --git a/SOOUM/SOOUM/Domain/Models/CommonNotificationInfo.swift b/SOOUM/SOOUM/Domain/Models/CommonNotificationInfo.swift index d980a38a..7139af52 100644 --- a/SOOUM/SOOUM/Domain/Models/CommonNotificationInfo.swift +++ b/SOOUM/SOOUM/Domain/Models/CommonNotificationInfo.swift @@ -34,7 +34,7 @@ extension CommonNotificationInfo { case deleted = "DELETED" case transferSuccess = "TRANSFER_SUCCESS" case follow = "FOLLOW" - case notice = "NOTICE" + case tagUsage = "TAG_USAGE" case none = "NONE" } } diff --git a/SOOUM/SOOUM/Domain/Models/CompositeNotificationInfo.swift b/SOOUM/SOOUM/Domain/Models/CompositeNotificationInfo.swift index 79a3e72d..22fef140 100644 --- a/SOOUM/SOOUM/Domain/Models/CompositeNotificationInfo.swift +++ b/SOOUM/SOOUM/Domain/Models/CompositeNotificationInfo.swift @@ -37,7 +37,7 @@ extension CompositeNotificationInfo: Decodable { case .feedLike, .commentLike, .commentWrite: let notification = try NotificationInfoResponse(from: decoder) self = .default(notification) - // TODO: NOTICE, TRANSFER_SUCCESS 는 아직 정해지지 않음 + // TODO: TRANSFER_SUCCESS, TAG_USAGE 는 아직 정해지지 않음 default: throw DecodingError.dataCorrupted( DecodingError.Context( diff --git a/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift b/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift index ce0a6315..ca7f39af 100644 --- a/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift +++ b/SOOUM/SOOUM/Extensions/Cocoa/Notification.swift @@ -18,6 +18,7 @@ extension Notification.Name { static let scollingToTopWithAnimation = Notification.Name("scollingToTopWithAnimation") /// Should reload static let reloadData = Notification.Name("reloadData") + static let reloadCommentsData = Notification.Name("reloadCommentsData") /// Updated report state static let updatedReportState = Notification.Name("updatedReportState") } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift index 0805f36d..666811e2 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewController.swift @@ -47,7 +47,7 @@ class NotificationViewController: BaseNavigationViewController, View { $0.delegate = self } - private lazy var tableView = UITableView(frame: .zero, style: .plain).then { + private lazy var tableView = UITableView(frame: .zero, style: .grouped).then { $0.backgroundColor = .som.v2.white $0.indicatorStyle = .black $0.separatorStyle = .none @@ -183,7 +183,7 @@ class NotificationViewController: BaseNavigationViewController, View { .subscribe(with: self) { object, pushInfo in let detailViewController = DetailViewController() detailViewController.reactor = reactor.reactorForDetail( - detailType: pushInfo.detailType, + entranceType: pushInfo.entranceType, with: pushInfo.id ) object.navigationPush(detailViewController, animated: true, bottomBarHidden: true) @@ -238,6 +238,15 @@ class NotificationViewController: BaseNavigationViewController, View { } .disposed(by: self.disposeBag) } + + + // MARK: Objc func + + @objc + private func reloadData(_ notification: Notification) { + + self.reactor?.action.onNext(.landing) + } } @@ -303,7 +312,7 @@ extension NotificationViewController: UITableViewDelegate { if case .feedLike = notification.notificationInfo.notificationType { return .init( - detailType: .feed, + entranceType: .feed, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, shouldRead: true @@ -311,7 +320,7 @@ extension NotificationViewController: UITableViewDelegate { } if case .commentLike = notification.notificationInfo.notificationType { return .init( - detailType: .comment, + entranceType: .comment, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, shouldRead: true @@ -319,18 +328,18 @@ extension NotificationViewController: UITableViewDelegate { } if case .commentWrite = notification.notificationInfo.notificationType { return .init( - detailType: .comment, + entranceType: .comment, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, shouldRead: true ) } return nil - /// follow, deleted, blocked 는 읽기 API만 호출 + /// follow, deleted, blocked 는 읽기 API만 호출 case let .follow(notification): return .init( - detailType: .feed, + entranceType: .feed, notificationId: notification.notificationInfo.notificationId, targetCardId: nil, shouldRead: true @@ -338,7 +347,7 @@ extension NotificationViewController: UITableViewDelegate { case let .deleted(notification): return .init( - detailType: .feed, + entranceType: .feed, notificationId: notification.notificationInfo.notificationId, targetCardId: nil, shouldRead: true @@ -346,7 +355,7 @@ extension NotificationViewController: UITableViewDelegate { case let .blocked(notification): return .init( - detailType: .feed, + entranceType: .feed, notificationId: notification.notificationInfo.notificationId, targetCardId: nil, shouldRead: true @@ -364,7 +373,7 @@ extension NotificationViewController: UITableViewDelegate { if case .feedLike = notification.notificationInfo.notificationType { return .init( - detailType: .feed, + entranceType: .feed, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, shouldRead: false @@ -372,7 +381,7 @@ extension NotificationViewController: UITableViewDelegate { } if case .commentLike = notification.notificationInfo.notificationType { return .init( - detailType: .comment, + entranceType: .comment, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, shouldRead: false @@ -380,7 +389,7 @@ extension NotificationViewController: UITableViewDelegate { } if case .commentWrite = notification.notificationInfo.notificationType { return .init( - detailType: .comment, + entranceType: .comment, notificationId: notification.notificationInfo.notificationId, targetCardId: notification.targetCardId, shouldRead: false @@ -453,6 +462,15 @@ extension NotificationViewController: UITableViewDelegate { } } + // group style이기 때문에 footer 제거 + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return nil + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return 0 + } + func tableView( _ tableView: UITableView, willDisplay cell: UITableViewCell, diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift index 98922d91..a718751a 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Notification/NotificationViewReactor.swift @@ -26,7 +26,7 @@ class NotificationViewReactor: Reactor { case notices([NoticeInfo]) case moreNotices([NoticeInfo]) case updateDisplayType(DisplayType) - case updatePushOrRequestReadInfo((detailType: DetailViewReactor.DetailType, id: String)?) + case updatePushOrRequestReadInfo((entranceType: EntranceCardType, id: String)?) case updateIsRefreshing(Bool) case updateIsReadSuccess(Bool) } @@ -36,7 +36,7 @@ class NotificationViewReactor: Reactor { fileprivate(set) var notificationsForUnread: [CompositeNotificationInfo]? fileprivate(set) var notifications: [CompositeNotificationInfo]? fileprivate(set) var notices: [NoticeInfo]? - fileprivate(set) var pushInfo: (detailType: DetailViewReactor.DetailType, id: String)? + fileprivate(set) var pushInfo: (entranceType: EntranceCardType, id: String)? fileprivate(set) var isRefreshing: Bool fileprivate(set) var isReadSuccess: Bool } @@ -127,38 +127,42 @@ class NotificationViewReactor: Reactor { /// 읽은 알림 여부 확인 if pushOrRequestReadInfo.shouldRead { - + /// 읽어야 하는 알림일 경우, 읽음 API 호출 return self.notificationUseCase.requestRead(notificationId: pushOrRequestReadInfo.notificationId) - .flatMapLatest { _ -> Observable in - - var concat: Observable { - /// 화면 전환할 카드 식별자 여부 확인 + .withUnretained(self) + .flatMapLatest { object, _ -> Observable in + /// 알림 화면 리로드 + return Observable.zip( + object.notificationUseCase.unreadNotifications(lastId: nil), + object.notificationUseCase.readNotifications(lastId: nil) + ) + .flatMapLatest { unreads, reads -> Observable in + if let targetCardId = pushOrRequestReadInfo.targetCardId { + return .concat([ + .just(.notifications(unreads: unreads, reads: reads)), .just(.updatePushOrRequestReadInfo(nil)), - .just(.updatePushOrRequestReadInfo((pushOrRequestReadInfo.detailType, targetCardId))) + .just(.updatePushOrRequestReadInfo((pushOrRequestReadInfo.entranceType, targetCardId))) ]) } else { - return .just(.updatePushOrRequestReadInfo(nil)) + return .concat([ + .just(.notifications(unreads: unreads, reads: reads)), + .just(.updatePushOrRequestReadInfo(nil)) + ]) } } - - return concat } } else { - var concat: Observable { - if let targetCardId = pushOrRequestReadInfo.targetCardId { - return .concat([ - .just(.updatePushOrRequestReadInfo(nil)), - .just(.updatePushOrRequestReadInfo((pushOrRequestReadInfo.detailType, targetCardId))) - ]) - } else { - return .just(.updatePushOrRequestReadInfo(nil)) - } + if let targetCardId = pushOrRequestReadInfo.targetCardId { + return .concat([ + .just(.updatePushOrRequestReadInfo(nil)), + .just(.updatePushOrRequestReadInfo((pushOrRequestReadInfo.entranceType, targetCardId))) + ]) + } else { + return .just(.updatePushOrRequestReadInfo(nil)) } - - return concat } } } @@ -227,7 +231,7 @@ extension NotificationViewReactor { } struct PushOrRequestReadInfo: Equatable { - let detailType: DetailViewReactor.DetailType + let entranceType: EntranceCardType let notificationId: String let targetCardId: String? let shouldRead: Bool @@ -273,10 +277,10 @@ extension NotificationViewReactor { } func canUpdatePushInfos( - prev prevPushInfo: (detailType: DetailViewReactor.DetailType, id: String), - curr currPushInfo: (detailType: DetailViewReactor.DetailType, id: String) + prev prevPushInfo: (entranceType: EntranceCardType, id: String), + curr currPushInfo: (entranceType: EntranceCardType, id: String) ) -> Bool { - return prevPushInfo.detailType == currPushInfo.detailType && + return prevPushInfo.entranceType == currPushInfo.entranceType && prevPushInfo.id == currPushInfo.id } @@ -293,7 +297,7 @@ extension NotificationViewReactor { extension NotificationViewReactor { - func reactorForDetail(detailType: DetailViewReactor.DetailType, with id: String) -> DetailViewReactor { - DetailViewReactor(dependencies: self.dependencies, detailType, type: .navi, with: id) + func reactorForDetail(entranceType: EntranceCardType, with id: String) -> DetailViewReactor { + DetailViewReactor(dependencies: self.dependencies, entranceType, type: .navi, with: id) } } From 31c13b5c8875cf573c23f7755bea68c5b732f260 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 02:29:31 +0900 Subject: [PATCH 26/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=ED=99=94=EB=A9=B4=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=20=EC=8B=9C=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Write/Views/Tags/WriteCardTagFooter.swift | 4 ++-- .../Presentations/Main/Write/Views/Tags/WriteCardTags.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift index a368ff28..e8e63872 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift @@ -148,14 +148,14 @@ extension WriteCardTagFooter { extension WriteCardTagFooter: UITextFieldDelegate { func textFieldDidBeginEditing(_ textField: UITextField) { - self.textField.text = nil + self.textField.text = textField.text == self.placeholder ? nil : textField.text self.imageView.image = .init(.icon(.v2(.outlined(.hash)))) self.imageView.tintColor = .som.v2.gray300 self.delegate?.textFieldDidBeginEditing(self) } func textFieldDidEndEditing(_ textField: UITextField) { - self.textField.text = self.placeholder + self.textField.text = (textField.text ?? "").isEmpty ? self.placeholder : textField.text self.imageView.image = .init(.icon(.v2(.outlined(.plus)))) self.imageView.tintColor = .som.v2.white self.delegate?.textFieldDidEndEditing(self) diff --git a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift index 045e4d7a..79b9af5d 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift @@ -311,7 +311,7 @@ extension WriteCardTags: WriteCardTagDelegate { extension WriteCardTags: WriteCardTagFooterDelegate { func textFieldDidBeginEditing(_ textField: WriteCardTagFooter) { - self.footerText = nil + self.footerText = textField.text self.collectionView.collectionViewLayout.invalidateLayout() self.delegate?.textFieldDidBeginEditing(textField) } From aefb2ea86330264b143c5c5accdbc3d78c4c4498 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 02:34:55 +0900 Subject: [PATCH 27/35] =?UTF-8?q?[=EB=B2=84=EC=A0=84]=20Develop=201.17.2(1?= =?UTF-8?q?017020)=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SOOUM/SOOUM.xcodeproj/project.pbxproj b/SOOUM/SOOUM.xcodeproj/project.pbxproj index 3e5e76a0..f0cebc30 100644 --- a/SOOUM/SOOUM.xcodeproj/project.pbxproj +++ b/SOOUM/SOOUM.xcodeproj/project.pbxproj @@ -3800,7 +3800,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017010; + CURRENT_PROJECT_VERSION = 1017020; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3823,7 +3823,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.1; + MARKETING_VERSION = 1.17.2; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3852,7 +3852,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017010; + CURRENT_PROJECT_VERSION = 1017020; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3875,7 +3875,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.1; + MARKETING_VERSION = 1.17.2; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; From c78f9daea41b4e027efd633ceccae7a40d11e085 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 03:14:00 +0900 Subject: [PATCH 28/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EC=B9=B4=EB=93=9C=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20?= =?UTF-8?q?=EB=B7=B0=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentations/Main/Home/Detail/Cells/DetailViewCell.swift | 1 + .../Presentations/Main/Home/Detail/DetailViewController.swift | 3 +++ 2 files changed, 4 insertions(+) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift index 18a60e5b..e425da1e 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift @@ -300,6 +300,7 @@ class DetailViewCell: UICollectionViewCell { self.memberInfoView.updateViewsWhenDeleted() self.likeAndCommentView.updateViewsWhenDeleted() self.backgroundImageView.removeFromSuperview() + self.tags.removeFromSuperview() self.deletedCardInDetailBackgroundView.isHidden = false } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index 221ff972..8580da06 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -385,6 +385,9 @@ class DetailViewController: BaseNavigationViewController, View { .observe(on: MainScheduler.asyncInstance) .subscribe(with: self) { object, _ in NotificationCenter.default.post(name: .reloadData, object: nil, userInfo: nil) + if reactor.entranceCardType == .comment { + NotificationCenter.default.post(name: .reloadCommentsData, object: nil, userInfo: nil) + } object.navigationBar.title = Text.deletedNavigationTitle object.navigationBar.setRightButtons([object.rightDeleteButton]) From 9eb200121ca174840547c6d0704b598909374dc2 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 03:16:05 +0900 Subject: [PATCH 29/35] =?UTF-8?q?[=EC=B6=94=EA=B0=80]=20=EC=82=AC=EC=A7=84?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnboardingProfileImageSettingViewController.swift | 6 ++++++ .../Presentations/Main/Write/WriteCardViewController.swift | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift b/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift index 4e8d3d3b..58ccddca 100644 --- a/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift +++ b/SOOUM/SOOUM/Presentations/Intro/Onboarding/ProfileImageSetting/OnboardingProfileImageSettingViewController.swift @@ -134,6 +134,12 @@ class OnboardingProfileImageSettingViewController: BaseNavigationViewController, } } + override func viewDidLoad() { + super.viewDidLoad() + + PHPhotoLibrary.requestAuthorization(for: .readWrite) { _ in } + } + // MARK: ReactorKit - bind diff --git a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift index 63d97985..b58314b3 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewController.swift @@ -167,6 +167,12 @@ class WriteCardViewController: BaseNavigationViewController, View { } } + override func viewDidLoad() { + super.viewDidLoad() + + PHPhotoLibrary.requestAuthorization(for: .readWrite) { _ in } + } + override func updatedKeyboard(withoutBottomSafeInset height: CGFloat) { super.updatedKeyboard(withoutBottomSafeInset: height) From 89ae606fd9c5362ea94932ff77b4307d5d3d39b4 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 03:16:25 +0900 Subject: [PATCH 30/35] =?UTF-8?q?[=EB=B2=84=EC=A0=84]=20Develop=201.17.3(1?= =?UTF-8?q?017030)=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SOOUM/SOOUM.xcodeproj/project.pbxproj b/SOOUM/SOOUM.xcodeproj/project.pbxproj index f0cebc30..fb9a0080 100644 --- a/SOOUM/SOOUM.xcodeproj/project.pbxproj +++ b/SOOUM/SOOUM.xcodeproj/project.pbxproj @@ -3800,7 +3800,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017020; + CURRENT_PROJECT_VERSION = 1017030; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3823,7 +3823,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.2; + MARKETING_VERSION = 1.17.3; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3852,7 +3852,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017020; + CURRENT_PROJECT_VERSION = 1017030; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3875,7 +3875,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.2; + MARKETING_VERSION = 1.17.3; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; From 56020aff0af8beb8bdb6be84377c830e0e00a28b Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 03:50:14 +0900 Subject: [PATCH 31/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=ED=83=AD=EB=B0=94?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=83=89=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/SOMTabBarController/SOMTabBarItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift b/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift index e4b70886..8dd5cb80 100644 --- a/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift +++ b/SOOUM/SOOUM/DesignSystem/Components/SOMTabBarController/SOMTabBarItem.swift @@ -17,7 +17,7 @@ class SOMTabBarItem: UIView { // MARK: Views private let imageView = UIImageView().then { - $0.tintColor = .som.gray300 + $0.tintColor = .som.v2.gray300 } private let titleLabel = UILabel().then { From ba3406888cf9855e6996ad48f7681bf50b746517 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 04:20:52 +0900 Subject: [PATCH 32/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Write/Views/Tags/WriteCardTagFooter.swift | 3 +-- .../Main/Write/Views/Tags/WriteCardTags.swift | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift index e8e63872..826334c3 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTagFooter.swift @@ -148,14 +148,13 @@ extension WriteCardTagFooter { extension WriteCardTagFooter: UITextFieldDelegate { func textFieldDidBeginEditing(_ textField: UITextField) { - self.textField.text = textField.text == self.placeholder ? nil : textField.text + self.textField.text = nil self.imageView.image = .init(.icon(.v2(.outlined(.hash)))) self.imageView.tintColor = .som.v2.gray300 self.delegate?.textFieldDidBeginEditing(self) } func textFieldDidEndEditing(_ textField: UITextField) { - self.textField.text = (textField.text ?? "").isEmpty ? self.placeholder : textField.text self.imageView.image = .init(.icon(.v2(.outlined(.plus)))) self.imageView.tintColor = .som.v2.white self.delegate?.textFieldDidEndEditing(self) diff --git a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift index 79b9af5d..6002b8a0 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/Views/Tags/WriteCardTags.swift @@ -317,7 +317,14 @@ extension WriteCardTags: WriteCardTagFooterDelegate { } func textFieldDidEndEditing(_ textField: WriteCardTagFooter) { - self.footerText = textField.text + if let text = textField.text, text.isEmpty == false { + let addedTag: WriteCardTagModel = .init(originalText: text, typography: self.typography) + var new = self.models + new.append(addedTag) + self.updateWrittenTags.accept(new) + } + textField.text = Text.tagPlaceholder + self.footerText = Text.tagPlaceholder self.collectionView.collectionViewLayout.invalidateLayout() self.scrollToRight(animated: true) } From 644561507bdc4ed815fe0d6add2d5d1d97a72819 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 04:21:15 +0900 Subject: [PATCH 33/35] =?UTF-8?q?[=EB=B3=80=EA=B2=BD]=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EB=B0=B1=EA=B7=B8=EB=9D=BC=EC=9A=B4?= =?UTF-8?q?=EB=93=9C=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentations/Main/Home/Detail/Cells/DetailViewCell.swift | 2 ++ .../Presentations/Main/Home/Detail/DetailViewController.swift | 2 +- .../Main/Home/Detail/Views/LikeAndCommentView.swift | 2 ++ .../Presentations/Main/Home/Detail/Views/MemberInfoView.swift | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift index e425da1e..c757c718 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Cells/DetailViewCell.swift @@ -139,6 +139,8 @@ class DetailViewCell: UICollectionViewCell { private func setupConstraints() { + self.backgroundColor = .som.v2.white + self.contentView.addSubview(self.memberInfoView) self.memberInfoView.snp.makeConstraints { $0.top.horizontalEdges.equalToSuperview() diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift index 8580da06..4fc50c0a 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/DetailViewController.swift @@ -77,7 +77,7 @@ class DetailViewController: BaseNavigationViewController, View { frame: .zero, collectionViewLayout: self.flowLayout ).then { - $0.backgroundColor = .som.v2.white + $0.backgroundColor = .som.v2.gray100 $0.alwaysBounceVertical = true $0.showsVerticalScrollIndicator = false diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift index 53db7858..b6d18655 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/LikeAndCommentView.swift @@ -91,6 +91,8 @@ class LikeAndCommentView: UIView { private func setupConstraints() { + self.backgroundColor = .som.v2.white + self.snp.makeConstraints { $0.height.equalTo(44) } diff --git a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift index 1fd813fc..e9e2ba95 100644 --- a/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift +++ b/SOOUM/SOOUM/Presentations/Main/Home/Detail/Views/MemberInfoView.swift @@ -112,6 +112,8 @@ class MemberInfoView: UIView { private func setupConstraints() { + self.backgroundColor = .som.v2.white + self.snp.makeConstraints { $0.height.equalTo(52) } From d9efc22d728ba233c1f0bddfbdf57ffe3b24c7d6 Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 04:21:44 +0900 Subject: [PATCH 34/35] =?UTF-8?q?[=EC=88=98=EC=A0=95]=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EC=8B=9C=20s3=20=EA=B3=B5=EA=B0=84=20?= =?UTF-8?q?=ED=99=95=EB=B3=B4=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SOOUM/Data/Repositories/CardRepositoryImpl.swift | 10 ++++++++++ .../Remotes/CardRemoteDataSourceImpl.swift | 11 +++++++++++ .../Remotes/Interfaces/CardRemoteDataSource.swift | 2 ++ .../Remotes/UserRemoteDataSourceImpl.swift | 2 +- SOOUM/SOOUM/Domain/Repositories/CardRepository.swift | 2 ++ SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift | 12 ++++++++++++ .../Domain/UseCases/Interfaces/CardUseCase.swift | 2 ++ .../Main/Write/WriteCardViewReactor.swift | 6 ++---- .../Resources/Alamofire/Request/V2/CardRequest.swift | 5 +++++ .../Resources/Alamofire/Request/V2/UserRequest.swift | 2 +- 10 files changed, 48 insertions(+), 6 deletions(-) diff --git a/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift b/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift index 12f950ac..3acb017f 100644 --- a/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift +++ b/SOOUM/SOOUM/Data/Repositories/CardRepositoryImpl.swift @@ -76,6 +76,16 @@ class CardRepositoryImpl: CardRepository { return self.remoteDataSource.defaultImages() } + func presignedURL() -> Observable { + + return self.remoteDataSource.presignedURL() + } + + func uploadImage(_ data: Data, with url: URL) -> Observable> { + + return self.remoteDataSource.uploadImage(data, with: url) + } + func writeCard( isDistanceShared: Bool, latitude: String?, diff --git a/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift b/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift index a14d00c7..a8f96462 100644 --- a/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift +++ b/SOOUM/SOOUM/Data/Repositories/Remotes/CardRemoteDataSourceImpl.swift @@ -86,6 +86,17 @@ class CardRemoteDataSourceImpl: CardRemoteDataSource { return self.provider.networkManager.fetch(DefaultImagesResponse.self, request: request) } + func presignedURL() -> Observable { + + let request: CardRequest = .presignedURL + return self.provider.networkManager.fetch(ImageUrlInfoResponse.self, request: request) + } + + func uploadImage(_ data: Data, with url: URL) -> Observable> { + + return self.provider.networkManager.upload(data, to: url) + } + func writeCard( isDistanceShared: Bool, latitude: String?, diff --git a/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift b/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift index 4f6a5547..5a76a74b 100644 --- a/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift +++ b/SOOUM/SOOUM/Data/Repositories/Remotes/Interfaces/CardRemoteDataSource.swift @@ -32,6 +32,8 @@ protocol CardRemoteDataSource { // MARK: Write func defaultImages() -> Observable + func presignedURL() -> Observable + func uploadImage(_ data: Data, with url: URL) -> Observable> func writeCard( isDistanceShared: Bool, latitude: String?, diff --git a/SOOUM/SOOUM/Data/Repositories/Remotes/UserRemoteDataSourceImpl.swift b/SOOUM/SOOUM/Data/Repositories/Remotes/UserRemoteDataSourceImpl.swift index 758e900c..b2906a13 100644 --- a/SOOUM/SOOUM/Data/Repositories/Remotes/UserRemoteDataSourceImpl.swift +++ b/SOOUM/SOOUM/Data/Repositories/Remotes/UserRemoteDataSourceImpl.swift @@ -43,7 +43,7 @@ class UserRemoteDataSourceImpl: UserRemoteDataSource { func presignedURL() -> Observable { let request: UserRequest = .presignedURL - return self.provider.networkManager.perform(ImageUrlInfoResponse.self, request: request) + return self.provider.networkManager.fetch(ImageUrlInfoResponse.self, request: request) } func uploadImage(_ data: Data, with url: URL) -> Observable> { diff --git a/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift b/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift index 2c8a5dbc..6cf18b6e 100644 --- a/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift +++ b/SOOUM/SOOUM/Domain/Repositories/CardRepository.swift @@ -32,6 +32,8 @@ protocol CardRepository { // MARK: Write func defaultImages() -> Observable + func presignedURL() -> Observable + func uploadImage(_ data: Data, with url: URL) -> Observable> func writeCard( isDistanceShared: Bool, latitude: String?, diff --git a/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift b/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift index a8906118..7949655b 100644 --- a/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift +++ b/SOOUM/SOOUM/Domain/UseCases/CardUseCaseImpl.swift @@ -76,6 +76,18 @@ class CardUseCaseImpl: CardUseCase { return self.repository.defaultImages().map { $0.defaultImages } } + func presignedURL() -> Observable { + + return self.repository.presignedURL().map { $0.imageUrlInfo } + } + + func uploadImage(_ data: Data, with url: URL) -> Observable { + + return self.repository.uploadImage(data, with: url) + .map { _ in true } + .catchAndReturn(false) + } + func writeCard( isDistanceShared: Bool, latitude: String?, diff --git a/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift b/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift index 740a92ff..9d3441c5 100644 --- a/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift +++ b/SOOUM/SOOUM/Domain/UseCases/Interfaces/CardUseCase.swift @@ -32,6 +32,8 @@ protocol CardUseCase { // MARK: Write func defaultImages() -> Observable + func presignedURL() -> Observable + func uploadImage(_ data: Data, with url: URL) -> Observable func writeCard( isDistanceShared: Bool, latitude: String?, diff --git a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift index 211f1d80..21c6bff9 100644 --- a/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift +++ b/SOOUM/SOOUM/Presentations/Main/Write/WriteCardViewReactor.swift @@ -60,7 +60,6 @@ class WriteCardViewReactor: Reactor { private let dependencies: AppDIContainerable private let cardUseCase: CardUseCase private let tagUseCase: TagUseCase - private let userUseCase: UserUseCase let locationManager: LocationManagerDelegate @@ -76,7 +75,6 @@ class WriteCardViewReactor: Reactor { self.dependencies = dependencies self.cardUseCase = dependencies.rootContainer.resolve(CardUseCase.self) self.tagUseCase = dependencies.rootContainer.resolve(TagUseCase.self) - self.userUseCase = dependencies.rootContainer.resolve(UserUseCase.self) self.locationManager = dependencies.rootContainer.resolve(ManagerProviderType.self).locationManager self.entranceType = entranceType self.parentCardId = parentCardId @@ -151,13 +149,13 @@ private extension WriteCardViewReactor { func uploadImage(_ image: UIImage) -> Observable { - return self.userUseCase.presignedURL() + return self.cardUseCase.presignedURL() .withUnretained(self) .flatMapLatest { object, presignedInfo -> Observable in if let imageData = image.jpegData(compressionQuality: 0.5), let url = URL(string: presignedInfo.imgUrl) { - return object.userUseCase.uploadImage(imageData, with: url) + return object.cardUseCase.uploadImage(imageData, with: url) .flatMapLatest { isSuccess -> Observable in let imageName = isSuccess ? presignedInfo.imgName : nil diff --git a/SOOUM/SOOUM/Resources/Alamofire/Request/V2/CardRequest.swift b/SOOUM/SOOUM/Resources/Alamofire/Request/V2/CardRequest.swift index e76ae1f4..7f7f324b 100644 --- a/SOOUM/SOOUM/Resources/Alamofire/Request/V2/CardRequest.swift +++ b/SOOUM/SOOUM/Resources/Alamofire/Request/V2/CardRequest.swift @@ -41,6 +41,8 @@ enum CardRequest: BaseRequest { /// 기본 이미지 조회 case defaultImages + /// 프로필 이미지 업로드할 공간 조회 + case presignedURL /// 글추가 case writeCard( isDistanceShared: Bool, @@ -113,6 +115,9 @@ enum CardRequest: BaseRequest { case .defaultImages: return "/api/images/defaults" + case .presignedURL: + + return "/api/images/card-img" case .writeCard: return "/api/cards" diff --git a/SOOUM/SOOUM/Resources/Alamofire/Request/V2/UserRequest.swift b/SOOUM/SOOUM/Resources/Alamofire/Request/V2/UserRequest.swift index 23a5701f..32e197e7 100644 --- a/SOOUM/SOOUM/Resources/Alamofire/Request/V2/UserRequest.swift +++ b/SOOUM/SOOUM/Resources/Alamofire/Request/V2/UserRequest.swift @@ -17,7 +17,7 @@ enum UserRequest: BaseRequest { case validateNickname(nickname: String) /// 닉네임 업데이트 case updateNickname(nickname: String) - /// 이미지 업로드할 공간 확보 + /// 프로필 이미지 업로드할 공간 조회 case presignedURL /// 이미지 업데이트 case updateImage(imageName: String) From 5fbbcf0d9b21e8344b64422ceb5742d88b25758b Mon Sep 17 00:00:00 2001 From: hyeonsik971029 Date: Thu, 6 Nov 2025 04:23:21 +0900 Subject: [PATCH 35/35] =?UTF-8?q?[=EB=B2=84=EC=A0=84]=20Develop=201.17.4(1?= =?UTF-8?q?017040)=20=EB=B2=84=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SOOUM/SOOUM.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SOOUM/SOOUM.xcodeproj/project.pbxproj b/SOOUM/SOOUM.xcodeproj/project.pbxproj index fb9a0080..1cbb1dd3 100644 --- a/SOOUM/SOOUM.xcodeproj/project.pbxproj +++ b/SOOUM/SOOUM.xcodeproj/project.pbxproj @@ -3800,7 +3800,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017030; + CURRENT_PROJECT_VERSION = 1017040; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3823,7 +3823,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.3; + MARKETING_VERSION = 1.17.4; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3852,7 +3852,7 @@ CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources/SOOUM-Dev.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1017030; + CURRENT_PROJECT_VERSION = 1017040; DEVELOPMENT_TEAM = 99FRG743RX; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist"; @@ -3875,7 +3875,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.3; + MARKETING_VERSION = 1.17.4; OTHER_SWIFT_FLAGS = "$(inherited) -D DEVELOP"; PRODUCT_BUNDLE_IDENTIFIER = com.sooum.dev; PRODUCT_NAME = "$(TARGET_NAME)";