From 79a37dc6df237db988f81890b538bddf67cd735f Mon Sep 17 00:00:00 2001 From: MinjaeLee <2alswo7@khu.ac.kr> Date: Mon, 2 Sep 2024 14:47:15 +0900 Subject: [PATCH 01/98] =?UTF-8?q?[Refactor]=20#195=20-=20WebView=20Block-B?= =?UTF-8?q?ased=20KVO=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 18 ++++++++++- .../Global/Protocols/ViewModelType.swift | 15 ++++++++++ .../LinkWebViewController.swift | 23 +++++--------- .../LinkWeb/ViewModel/LinkWebViewModel.swift | 30 +++++++++++++++++++ 4 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 TOASTER-iOS/Global/Protocols/ViewModelType.swift create mode 100644 TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index a9aca337..fe530ffa 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 3913B0B02BCECFC80031A3EB /* UpdateAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */; }; 391908422B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */; }; 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */; }; + 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7EC72C855F180034A14E /* ViewModelType.swift */; }; + 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */; }; 398ACFDC2B5E77FA00D5EE77 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */; }; 398BE7F32B456367001595E0 /* ToasterToastMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */; }; 398BE7F62B456AF9001595E0 /* BottomType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F52B456AF9001595E0 /* BottomType.swift */; }; @@ -323,6 +325,8 @@ 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateAlertType.swift; sourceTree = ""; }; 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditPriorityCategoryRequestDTO.swift; sourceTree = ""; }; 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditNameCategoryRequestDTO.swift; sourceTree = ""; }; + 396D7EC72C855F180034A14E /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; + 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebViewModel.swift; sourceTree = ""; }; 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterToastMessageView.swift; sourceTree = ""; }; 398BE7F52B456AF9001595E0 /* BottomType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomType.swift; sourceTree = ""; }; @@ -538,8 +542,9 @@ isa = PBXGroup; children = ( 3909A0692B6236FE005A4546 /* Model */, - 390925C92B4F047500487AA3 /* View */, 39A843D72B7485DF007A4D75 /* ViewController */, + 390925C92B4F047500487AA3 /* View */, + 396D7EC92C855F4F0034A14E /* ViewModel */, ); path = LinkWeb; sourceTree = ""; @@ -601,6 +606,14 @@ path = UpdateAlert; sourceTree = ""; }; + 396D7EC92C855F4F0034A14E /* ViewModel */ = { + isa = PBXGroup; + children = ( + 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 398ACFDA2B5E77C500D5EE77 /* Assets */ = { isa = PBXGroup; children = ( @@ -939,6 +952,7 @@ isa = PBXGroup; children = ( 3F2FA1762B45C3E000EDBF95 /* AuthenticationAdapterProtocol.swift */, + 396D7EC72C855F180034A14E /* ViewModelType.swift */, ); path = Protocols; sourceTree = ""; @@ -1902,6 +1916,7 @@ 6BE6DA512B50B309008B06FA /* PatchPushAlarmRequestDTO.swift in Sources */, 830517962B4D21BB009FFB60 /* CompositioinalFactory.swift in Sources */, 6BE6D9E82B4EA773008B06FA /* RemindCollectionFooterView.swift in Sources */, + 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */, 39A843CA2B74512B007A4D75 /* DetailClipViewModel.swift in Sources */, 83CFC3392B568BE700A2EB2B /* RecommendSiteModel.swift in Sources */, 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */, @@ -1953,6 +1968,7 @@ 3F2FA17D2B4928B700EDBF95 /* LoginUseCase.swift in Sources */, 6BE6DA9A2B54747B008B06FA /* TimerAPIService.swift in Sources */, 6BE6DA882B546B24008B06FA /* PatchOpenLinkResponseDTO.swift in Sources */, + 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */, 6BE6DA752B50C373008B06FA /* GetCheckCategoryResponseDTO.swift in Sources */, 6BE6D9EF2B4EE98D008B06FA /* RemindModel.swift in Sources */, 6BE6DA6B2B50BFED008B06FA /* NoneDataResponseDTO.swift in Sources */, diff --git a/TOASTER-iOS/Global/Protocols/ViewModelType.swift b/TOASTER-iOS/Global/Protocols/ViewModelType.swift new file mode 100644 index 00000000..c3346a58 --- /dev/null +++ b/TOASTER-iOS/Global/Protocols/ViewModelType.swift @@ -0,0 +1,15 @@ +// +// ViewModelType.swift +// TOASTER-iOS +// +// Created by 민 on 9/2/24. +// + +import Foundation + +protocol ViewModelType { + associatedtype Input + associatedtype Output + + func transform(_ input: Input) -> Output +} diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index dd5bf094..d7b7094b 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -15,6 +15,8 @@ final class LinkWebViewController: UIViewController { // MARK: - Properties + private var progressObservation: NSKeyValueObservation? + private var canGoBack: Bool = false { didSet { backButton.isEnabled = canGoBack @@ -71,10 +73,6 @@ final class LinkWebViewController: UIViewController { showNavigationBar() } - - deinit { - webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) - } } // MARK: - Extensions @@ -97,16 +95,6 @@ extension LinkWebViewController { } toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, safariButton], animated: false) } - - /// KVO를 사용하여 estimatedProgress가 변경될 때 호출되는 메서드 - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - if keyPath == "estimatedProgress" { - if let newProgress = change?[.newKey] as? NSNumber { - let progress = Float(truncating: newProgress) - progressView.progress = progress - } - } - } } // MARK: - Private Extensions @@ -123,7 +111,12 @@ private extension LinkWebViewController { webView.do { $0.navigationDelegate = self - $0.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil) + progressObservation = $0.observe( + \.estimatedProgress, + options: [.new]) { [weak self] object, _ in + let progress = Float(object.estimatedProgress) + self?.progressView.progress = progress + } } toolBar.do { diff --git a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift new file mode 100644 index 00000000..67840ac1 --- /dev/null +++ b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift @@ -0,0 +1,30 @@ +// +// LinkWebViewModel.swift +// TOASTER-iOS +// +// Created by 민 on 9/2/24. +// + +import Foundation + +enum LinkWebViewModelInput { + +} + +enum LinkWebViewModelOutput { + +} + +final class LinkWebViewModel: ViewModelType { + + typealias Input = LinkWebViewModelInput + typealias Output = LinkWebViewModelOutput + + func transform(_ input: Input) -> Output { + switch input { + + } + } +} + + From 88eca1e4dbf41d10cbba60ae2a7750bcdba81f31 Mon Sep 17 00:00:00 2001 From: MinjaeLee <2alswo7@khu.ac.kr> Date: Wed, 4 Sep 2024 13:50:00 +0900 Subject: [PATCH 02/98] =?UTF-8?q?[Chore]=20#195=20-=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EB=B7=B0=20=ED=88=B4=EB=B0=94=20VC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B3=84=EB=8F=84=EC=9D=98=20View=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 + .../LinkWeb/View/LinkWebToolBarView.swift | 167 ++++++++++++++++++ .../LinkWebViewController.swift | 107 ++++------- 3 files changed, 209 insertions(+), 69 deletions(-) create mode 100644 TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 1753a207..ecd5c5ff 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */; }; 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7EC72C855F180034A14E /* ViewModelType.swift */; }; 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */; }; + 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */; }; 398ACFDC2B5E77FA00D5EE77 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */; }; 398BE7F32B456367001595E0 /* ToasterToastMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */; }; 398BE7F62B456AF9001595E0 /* BottomType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F52B456AF9001595E0 /* BottomType.swift */; }; @@ -327,6 +328,7 @@ 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditNameCategoryRequestDTO.swift; sourceTree = ""; }; 396D7EC72C855F180034A14E /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebViewModel.swift; sourceTree = ""; }; + 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebToolBarView.swift; sourceTree = ""; }; 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterToastMessageView.swift; sourceTree = ""; }; 398BE7F52B456AF9001595E0 /* BottomType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomType.swift; sourceTree = ""; }; @@ -561,6 +563,7 @@ isa = PBXGroup; children = ( 390925CA2B4F049800487AA3 /* LinkWebNavigationView.swift */, + 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */, ); path = View; sourceTree = ""; @@ -1973,6 +1976,7 @@ 6BE6D9EF2B4EE98D008B06FA /* RemindModel.swift in Sources */, 6BE6DA6B2B50BFED008B06FA /* NoneDataResponseDTO.swift in Sources */, 391908422B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift in Sources */, + 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */, 6BE6DA732B50C33A008B06FA /* GetAllCategoryResponseDTO.swift in Sources */, 3F2FA1752B45C0AF00EDBF95 /* Config.swift in Sources */, 6B6AE6AC2B3FF6F7000E2366 /* UIColor+.swift in Sources */, diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift new file mode 100644 index 00000000..34579f14 --- /dev/null +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -0,0 +1,167 @@ +// +// LinkWebToolBarView.swift +// TOASTER-iOS +// +// Created by 민 on 9/4/24. +// + +import UIKit + +import SnapKit + +final class LinkWebToolBarView: UIView { + + // MARK: - Properties + + private var canGoBack: Bool = false { + didSet { + backButton.isEnabled = canGoBack + backButton.tintColor = canGoBack ? .gray700 : .gray150 + } + } + + private var canGoForward: Bool = false { + didSet { + forwardButton.isEnabled = canGoForward + forwardButton.tintColor = canGoForward ? .gray700 : .gray150 + } + } + + private(set) var isRead: Bool = false { + didSet { + readLinkCheckButton.tintColor = isRead ? .gray700 : .gray150 + } + } + + private var backButtonAction: (() -> Void)? + private var forwardButtonAction: (() -> Void)? + private var readLinkCheckButtonAction: (() -> Void)? + private var safariButtonAction: (() -> Void)? + + // MARK: - UI Components + + private let toolBar = UIToolbar() + + private let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + private let backButton = UIBarButtonItem() + private let forwardButton = UIBarButtonItem() + private let readLinkCheckButton = UIBarButtonItem() + private let safariButton = UIBarButtonItem() + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + + setupStyle() + setupHierarchy() + setupLayout() + setupAddTarget() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Extensions + +extension LinkWebToolBarView { + func backButtonTapped(_ action: @escaping () -> Void) { + backButtonAction = action + } + + func forwardButtonTapped(_ action: @escaping () -> Void) { + forwardButtonAction = action + } + + func readLinkCheckButtonTapped(_ action: @escaping () -> Void) { + readLinkCheckButtonAction = action + } + + func safariButtonTapped(_ action: @escaping () -> Void) { + safariButtonAction = action + } + + func updateCanGoBack(_ canGoBack: Bool) { + self.canGoBack = canGoBack + } + + func updateCanGoForward(_ canGoForward: Bool) { + self.canGoForward = canGoForward + } + + func updateIsRead(_ isRead: Bool) { + self.isRead = isRead + } +} + +// MARK: - Private Extensions + +private extension LinkWebToolBarView { + func setupStyle() { + toolBar.do { + $0.backgroundColor = .toasterWhite + $0.setBackgroundImage(UIImage(), forToolbarPosition: .bottom, barMetrics: .default) + $0.setShadowImage(UIImage(), forToolbarPosition: .bottom) + } + + backButton.do { + $0.tintColor = .gray700 + $0.image = .icArrow2Back24 + $0.style = .plain + } + + forwardButton.do { + $0.tintColor = .gray700 + $0.image = .icArrow2Forward24 + $0.style = .plain + } + + readLinkCheckButton.do { + $0.image = .icRead + $0.style = .plain + } + + safariButton.do { + $0.tintColor = .gray700 + $0.image = .icSafari24 + $0.style = .plain + } + } + + func setupHierarchy() { + addSubview(toolBar) + toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, safariButton], animated: false) + } + + func setupLayout() { + toolBar.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + func setupAddTarget() { + [backButton, forwardButton, readLinkCheckButton, safariButton].forEach { + $0.target = self + $0.action = #selector(barButtonTapped) + } + } + + @objc + func barButtonTapped(_ sender: UIBarButtonItem) { + switch sender { + case backButton: + backButtonAction?() + case forwardButton: + forwardButtonAction?() + case readLinkCheckButton: + readLinkCheckButtonAction?() + case safariButton: + safariButtonAction?() + default: + break + } + } +} diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index d7b7094b..73effb7d 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -16,26 +16,6 @@ final class LinkWebViewController: UIViewController { // MARK: - Properties private var progressObservation: NSKeyValueObservation? - - private var canGoBack: Bool = false { - didSet { - backButton.isEnabled = canGoBack - backButton.tintColor = canGoBack ? .gray700 : .gray150 - } - } - - private var canGoForward: Bool = false { - didSet { - forwardButton.isEnabled = canGoForward - forwardButton.tintColor = canGoForward ? .gray700 : .gray150 - } - } - - private var isRead: Bool = false { - didSet { - readLinkCheckButton.tintColor = isRead ? .gray700 : .gray150 - } - } private var toastId: Int? // MARK: - UI Properties @@ -43,13 +23,7 @@ final class LinkWebViewController: UIViewController { private let navigationView = LinkWebNavigationView() private let progressView = UIProgressView() private let webView = WKWebView() - private let toolBar = UIToolbar() - - private let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - private lazy var backButton = UIBarButtonItem(image: .icArrow2Back24, style: .plain, target: self, action: #selector(goBackInWeb)) - private lazy var forwardButton = UIBarButtonItem(image: .icArrow2Forward24, style: .plain, target: self, action: #selector(goForwardInWeb)) - private lazy var readLinkCheckButton = UIBarButtonItem(image: .icRead, style: .plain, target: self, action: #selector(checkReadInWeb)) - private lazy var safariButton = UIBarButtonItem(image: .icSafari24, style: .plain, target: self, action: #selector(openInSafari)) + private let toolBar = LinkWebToolBarView() // MARK: - Life Cycle @@ -60,6 +34,7 @@ final class LinkWebViewController: UIViewController { setupHierarchy() setupLayout() setupNavigationBarAction() + setupToolBarAction() } override func viewWillAppear(_ animated: Bool) { @@ -73,6 +48,10 @@ final class LinkWebViewController: UIViewController { showNavigationBar() } + + deinit { + progressObservation?.invalidate() + } } // MARK: - Extensions @@ -83,9 +62,8 @@ extension LinkWebViewController { let request = URLRequest(url: url) webView.load(request) } - self.isRead = isRead + toolBar.updateIsRead(isRead) self.toastId = id - toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, safariButton], animated: false) } func setupDataBind(linkURL: String) { @@ -93,7 +71,6 @@ extension LinkWebViewController { let request = URLRequest(url: url) webView.load(request) } - toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, safariButton], animated: false) } } @@ -116,17 +93,7 @@ private extension LinkWebViewController { options: [.new]) { [weak self] object, _ in let progress = Float(object.estimatedProgress) self?.progressView.progress = progress - } - } - - toolBar.do { - $0.backgroundColor = .toasterWhite - $0.setBackgroundImage(UIImage(), forToolbarPosition: .bottom, barMetrics: .default) - $0.setShadowImage(UIImage(), forToolbarPosition: .bottom) - } - - [backButton, forwardButton, safariButton].forEach { - $0.tintColor = .gray700 + } } } @@ -173,33 +140,35 @@ private extension LinkWebViewController { } } - /// 툴바 뒤로가기 버튼 클릭 시 - @objc func goBackInWeb() { - if webView.canGoBack { - webView.goBack() - canGoBack = webView.canGoBack + func setupToolBarAction() { + /// 툴바 뒤로가기 버튼 클릭 액션 클로저 + toolBar.backButtonTapped { + if self.webView.canGoBack { + self.webView.goBack() + self.toolBar.updateCanGoBack(self.webView.canGoBack) + } } - } - - /// 툴바 앞으로가기 버튼 클릭 시 - @objc func goForwardInWeb() { - if webView.canGoForward { - webView.goForward() - canGoForward = webView.canGoForward + + /// 툴바 앞으로가기 버튼 클릭 액션 클로저 + toolBar.forwardButtonTapped { + if self.webView.canGoForward { + self.webView.goForward() + self.toolBar.updateCanGoForward(self.webView.canGoForward) + } } - } - - /// 툴바 링크 확인 완료 버튼 클릭 시 - @objc func checkReadInWeb() { - if let toastId { - patchOpenLinkAPI(requestBody: LinkReadEditModel(toastId: toastId, isRead: !isRead)) + + /// 툴바 링크 확인완료 버튼 클릭 액션 클로저 + toolBar.readLinkCheckButtonTapped { + if let toastId = self.toastId { + self.patchOpenLinkAPI( + requestBody: LinkReadEditModel(toastId: toastId, isRead: !self.toolBar.isRead) + ) + } } - } - - /// 툴바 사파리 버튼 클릭 시 - @objc func openInSafari() { - if let url = webView.url { - UIApplication.shared.open(url) + + /// 툴바 사파리 버튼 클릭 액션 클로저 + toolBar.safariButtonTapped { + if let url = self.webView.url { UIApplication.shared.open(url) } } } } @@ -212,8 +181,8 @@ extension LinkWebViewController: WKNavigationDelegate { if let url = webView.url?.absoluteString { navigationView.setupLinkAddress(link: url) } - canGoBack = webView.canGoBack - canGoForward = webView.canGoForward + toolBar.updateCanGoBack(webView.canGoBack) + toolBar.updateCanGoForward(webView.canGoForward) } /// 웹 페이지 로딩이 완료되었을 때 호출 @@ -235,12 +204,12 @@ extension LinkWebViewController { isRead: requestBody.isRead)) { result in switch result { case .success: - if !self.isRead { + if !self.toolBar.isRead { self.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.completeReadLink) } else { self.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.cancelReadLink) } - self.isRead = !self.isRead + self.toolBar.updateIsRead(!self.toolBar.isRead) case .unAuthorized, .networkFail, .notFound: self.changeViewController(viewController: LoginViewController()) default: return From 1349d7dfee015ac571633a5dfa2a8813bed7bc97 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Fri, 20 Sep 2024 16:36:40 +0900 Subject: [PATCH 03/98] [Chore] #201 - AddLinkViewModel --- TOASTER-iOS.xcodeproj/project.pbxproj | 12 ++++++++ .../ViewModel/AddLinkViewModel.swift | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 50bcf059..74f33752 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -270,6 +270,7 @@ 8315CD8C2B54782F0061F377 /* SelectClipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD8B2B54782F0061F377 /* SelectClipViewController.swift */; }; 8315CD8E2B547EE30061F377 /* SelectClipHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD8D2B547EE30061F377 /* SelectClipHeaderView.swift */; }; 8315CD912B5521F70061F377 /* SelectClipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD902B5521F70061F377 /* SelectClipModel.swift */; }; + 832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */; }; 83474A6A2BED06EB009B9C48 /* ToasterTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7B2B54571D008B06FA /* ToasterTargetType.swift */; }; 83474A6B2BED06F1009B9C48 /* PatchEditLinkTitleRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */; }; 83474A6C2BED072A009B9C48 /* ToasterAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7D2B54572B008B06FA /* ToasterAPIService.swift */; }; @@ -485,6 +486,7 @@ 8315CD8B2B54782F0061F377 /* SelectClipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipViewController.swift; sourceTree = ""; }; 8315CD8D2B547EE30061F377 /* SelectClipHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipHeaderView.swift; sourceTree = ""; }; 8315CD902B5521F70061F377 /* SelectClipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipModel.swift; sourceTree = ""; }; + 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLinkViewModel.swift; sourceTree = ""; }; 8364220B2BE7BFB2005C4085 /* PatchEditLinkTitleResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleResponseDTO.swift; sourceTree = ""; }; 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleRequestDTO.swift; sourceTree = ""; }; 8388E98D2BC8FC6700858C5C /* EditLinkBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLinkBottomSheetView.swift; sourceTree = ""; }; @@ -1540,6 +1542,7 @@ children = ( 8309F5862B8DCE8100A1420A /* Model */, 8309F5852B8DCE7B00A1420A /* View */, + 832F0ED52C9C07C500E38571 /* ViewModel */, ); path = LinkEmbed; sourceTree = ""; @@ -1596,6 +1599,14 @@ path = ViewModel; sourceTree = ""; }; + 832F0ED52C9C07C500E38571 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1868,6 +1879,7 @@ 3F617CB82B4ECB6000956E69 /* MypageUserModel.swift in Sources */, 6BE6DA612B50B742008B06FA /* ClipAPIService.swift in Sources */, 830517AA2B4D95E9009FFB60 /* HomeFooterCollectionView.swift in Sources */, + 832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */, 39049C8D2B43EEF400C9196E /* ToastStatus.swift in Sources */, 8305178E2B4D1EF8009FFB60 /* WeeklyRecommendCollectionViewCell.swift in Sources */, 830517902B4D1FC7009FFB60 /* HomeHeaderCollectionView.swift in Sources */, diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift new file mode 100644 index 00000000..b147ee0d --- /dev/null +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -0,0 +1,28 @@ +// +// AddLinkViewModel.swift +// TOASTER-iOS +// +// Created by Gahyun Kim on 9/19/24. +// + +import UIKit + +protocol AddLinkViewModelInputs { + // Textfield Text +} + +protocol AddLinkViewModelOutputs { + // Next Button Enabled + // Next Button BackgroundColor + // Textfield BorderColor + // Textfield Error Message - Link effectiveness +} + +protocol AddLinkViewModelType { + var inputs: AddLinkViewModelInputs { get } + var outputs: AddLinkViewModelOutpus { get } +} + +final class AddLinkViewModel { + +} From d34334831fc9b79d0355ad3e6250ca1f19d6617c Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 24 Sep 2024 14:51:06 +0900 Subject: [PATCH 04/98] [Chore] #201 - Change to AddLinkViewModel Input Output structure --- .../ViewModel/AddLinkViewModel.swift | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index b147ee0d..5ddf6368 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -8,21 +8,61 @@ import UIKit protocol AddLinkViewModelInputs { - // Textfield Text -} + func embedLinkText(_ text: String)} protocol AddLinkViewModelOutputs { - // Next Button Enabled - // Next Button BackgroundColor - // Textfield BorderColor - // Textfield Error Message - Link effectiveness + var isNextButtonEnabled: Bool { get } + var nextButtonBackgroundColor: UIColor { get } + var textFieldBorderColor: UIColor { get } + var linkEffectivenessMessage: String? { get } } protocol AddLinkViewModelType { var inputs: AddLinkViewModelInputs { get } - var outputs: AddLinkViewModelOutpus { get } + var outputs: AddLinkViewModelOutputs { get } +} + +final class AddLinkViewModel: AddLinkViewModelType, AddLinkViewModelInputs, AddLinkViewModelOutputs { + + // Input + private var embedLink: String = "" { + didSet { + updateOutputs() + } + } + + // Output + var isNextButtonEnabled: Bool + var nextButtonBackgroundColor: UIColor + var textFieldBorderColor: UIColor + var linkEffectivenessMessage: String? + + init() { + self.isNextButtonEnabled = false + self.nextButtonBackgroundColor = .gray200 + self.textFieldBorderColor = .clear + self.linkEffectivenessMessage = nil + } + + func embedLinkText(_ text: String) { + embedLink = text + } + + var inputs: AddLinkViewModelInputs { return self } + var outputs: AddLinkViewModelOutputs { return self } } -final class AddLinkViewModel { +private extension AddLinkViewModel { + func updateOutputs() { + let isValid = isValidURL(embedLink) + isNextButtonEnabled = !embedLink.isEmpty && isValid + nextButtonBackgroundColor = isNextButtonEnabled ? .black850 : .gray200 + textFieldBorderColor = isValid ? .clear : UIColor.toasterError + linkEffectivenessMessage = isValid ? nil : (embedLink.isEmpty ? "링크를 입력해주세요" : "유효하지 않은 형식의 링크입니다.") + } + func isValidURL(_ urlString: String) -> Bool { + guard let url = URL(string: urlString) else { return false } + return UIApplication.shared.canOpenURL(url) + } } From 542ed9a21923269f2aee71b3df2a110aac5f69c6 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 24 Sep 2024 15:30:44 +0900 Subject: [PATCH 05/98] [Chore] #201 - URL Validation --- .../AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index 5ddf6368..dd8e2bcf 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -47,7 +47,7 @@ final class AddLinkViewModel: AddLinkViewModelType, AddLinkViewModelInputs, AddL func embedLinkText(_ text: String) { embedLink = text } - + var inputs: AddLinkViewModelInputs { return self } var outputs: AddLinkViewModelOutputs { return self } } @@ -62,7 +62,10 @@ private extension AddLinkViewModel { } func isValidURL(_ urlString: String) -> Bool { - guard let url = URL(string: urlString) else { return false } - return UIApplication.shared.canOpenURL(url) + if (urlString.prefix(8) == "https://") || (urlString.prefix(7) == "http://") { + return true + } else { + return false + } } } From 11c2cf4abeda327d68eb14b9d0bcc0ac0e2b45ce Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 25 Sep 2024 09:15:36 +0900 Subject: [PATCH 06/98] [Chore] #201 - Clear Button Output --- .../AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index dd8e2bcf..ba68100f 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -11,6 +11,7 @@ protocol AddLinkViewModelInputs { func embedLinkText(_ text: String)} protocol AddLinkViewModelOutputs { + var isClearButtonHidden: Bool { get } var isNextButtonEnabled: Bool { get } var nextButtonBackgroundColor: UIColor { get } var textFieldBorderColor: UIColor { get } @@ -32,12 +33,14 @@ final class AddLinkViewModel: AddLinkViewModelType, AddLinkViewModelInputs, AddL } // Output + var isClearButtonHidden: Bool var isNextButtonEnabled: Bool var nextButtonBackgroundColor: UIColor var textFieldBorderColor: UIColor var linkEffectivenessMessage: String? init() { + self.isClearButtonHidden = true self.isNextButtonEnabled = false self.nextButtonBackgroundColor = .gray200 self.textFieldBorderColor = .clear @@ -55,6 +58,7 @@ final class AddLinkViewModel: AddLinkViewModelType, AddLinkViewModelInputs, AddL private extension AddLinkViewModel { func updateOutputs() { let isValid = isValidURL(embedLink) + isClearButtonHidden = embedLink.isEmpty isNextButtonEnabled = !embedLink.isEmpty && isValid nextButtonBackgroundColor = isNextButtonEnabled ? .black850 : .gray200 textFieldBorderColor = isValid ? .clear : UIColor.toasterError From 5c63a607048350c43cddcaba7a768aa076ae9dab Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 25 Sep 2024 09:16:21 +0900 Subject: [PATCH 07/98] [Chore] #201 - ViewModel Binding --- .../AddLink/LinkEmbed/View/AddLinkView.swift | 121 ++++-------------- .../View/AddLinkViewController.swift | 51 +++++++- 2 files changed, 70 insertions(+), 102 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift index 0d72bd4e..13524c0a 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift @@ -21,7 +21,7 @@ final class AddLinkView: UIView { private let descriptLabel = UILabel() var linkEmbedTextField = UITextField() - private let clearButton = UIButton() + let clearButton = UIButton() let nextBottomButton = UIButton() let nextTopButton = UIButton() @@ -164,109 +164,40 @@ private extension AddLinkView { // MARK: - Extension extension AddLinkView: UITextFieldDelegate { - - // MARK: - Timer Check - - func textFieldDidBeginEditing(_ textField: UITextField) { - // 텍스트 필드에 입력이 시작될 때 호출되는 메서드 - clearButton.isHidden = false - nextTopButton.backgroundColor = .black850 - linkEmbedTextField.placeholder = nil - - // 여기서 타이머를 시작하고, 0.5초 후에 텍스트를 확인 후 텍스트필드 에러 처리 - if textField.text?.count ?? 0 > 1 { - startTimer() - } - } - - func textField(_ textField: UITextField, - shouldChangeCharactersIn range: NSRange, - replacementString string: String) -> Bool { - - // 입력이 발생할 때마다 호출되는 메서드 - // 여기서 타이머를 재시작 - restartTimer() - return true - } - - func startTimer() { - // 0.5초 후에 checkTextField 메서드 호출 - timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in - // URL 유효 여부 판단 후 처리 - if let urlText = self?.linkEmbedTextField.text { - if (urlText.prefix(8) == "https://") || (urlText.prefix(7) == "http://") { - self?.resetError() - } else { - self?.isValidLinkError() - } - } - } - } - - func restartTimer() { - // 타이머 재시작 - stopTimer() - startTimer() - } - - func stopTimer() { - // 타이머를 정지, 테두리 초기화 - timer?.invalidate() - linkEmbedTextField.layer.borderColor = UIColor.clear.cgColor - } - - // MARK: - Text Field Error - - // 링크를 입력하는 텍스트필드가 비어 있을 경우 error 처리 - func emptyError() { - linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor - linkEmbedTextField.layer.borderWidth = 1 - - // Button 비활성화 - nextTopButton.backgroundColor = .gray200 - nextBottomButton.backgroundColor = .gray200 - nextTopButton.isEnabled = false - nextBottomButton.isEnabled = false - - errorLabel.text = "링크를 입력해주세요" - addSubview(errorLabel) - errorLabel.snp.makeConstraints { - $0.top.equalTo(linkEmbedTextField.snp.bottom).offset(6) - $0.leading.equalTo(linkEmbedTextField.snp.leading) - } + +// // 링크를 입력하는 텍스트필드가 비어 있을 경우 error 처리 +// func emptyError() { +// linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor +// linkEmbedTextField.layer.borderWidth = 1 +// +// // Button 비활성화 +// nextTopButton.backgroundColor = .gray200 +// nextBottomButton.backgroundColor = .gray200 +// nextTopButton.isEnabled = false +// nextBottomButton.isEnabled = false +// +// errorLabel.text = "링크를 입력해주세요" +// addSubview(errorLabel) +// errorLabel.snp.makeConstraints { +// $0.top.equalTo(linkEmbedTextField.snp.bottom).offset(6) +// $0.leading.equalTo(linkEmbedTextField.snp.leading) +// } +// errorLabel.isHidden = false +// } +} + +extension AddLinkView { + func isValidLinkError(_ message: String) { + errorLabel.text = message errorLabel.isHidden = false - } - - // 링크가 유효하지 않을 경우 error 처리 - func isValidLinkError() { - linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor - linkEmbedTextField.layer.borderWidth = 1 - - // Button 비활성화 - nextTopButton.backgroundColor = .gray200 - nextBottomButton.backgroundColor = .gray200 - nextTopButton.isEnabled = false - nextBottomButton.isEnabled = false - - errorLabel.text = "유효하지 않은 형식의 링크입니다" addSubview(errorLabel) errorLabel.snp.makeConstraints { $0.top.equalTo(linkEmbedTextField.snp.bottom).offset(6) $0.leading.equalTo(linkEmbedTextField.snp.leading) } - errorLabel.isHidden = false } - // 링크가 유효할 경우, error reset func resetError() { - linkEmbedTextField.layer.borderColor = UIColor.clear.cgColor - - // Button 활성화 - nextTopButton.backgroundColor = .black850 - nextBottomButton.backgroundColor = .black850 - nextTopButton.isEnabled = true - nextBottomButton.isEnabled = true - errorLabel.isHidden = true } } diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index 74fd5620..cd1a449f 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -33,6 +33,7 @@ final class AddLinkViewController: UIViewController { // MARK: - UI Properties private var addLinkView = AddLinkView() + private var viewModel = AddLinkViewModel() // MARK: - Life Cycle @@ -42,6 +43,9 @@ final class AddLinkViewController: UIViewController { setupStyle() setAddLinkVew() hideKeyboard() + + setupBinding() + updateUI() } override func viewWillAppear(_ animated: Bool) { @@ -123,16 +127,49 @@ private extension AddLinkViewController { } @objc func tappedNextBottomButton() { - if (addLinkView.linkEmbedTextField.text?.count ?? 0) < 1 { - addLinkView.emptyError() + + let selectClipViewController = SelectClipViewController() + selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? "" + selectClipViewController.delegate = self + self.navigationController?.pushViewController(selectClipViewController, animated: true) + +// if (addLinkView.linkEmbedTextField.text?.count ?? 0) < 1 { +// addLinkView.emptyError() +// } else { +// let selectClipViewController = SelectClipViewController() +// selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? "" +// selectClipViewController.delegate = self +// self.navigationController?.pushViewController(selectClipViewController, animated: true) +// } + } + +} + +extension AddLinkViewController { + private func setupBinding() { + addLinkView.linkEmbedTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + } + + @objc private func textFieldDidChange(_ textField: UITextField) { + viewModel.inputs.embedLinkText(textField.text ?? "") + updateUI() + } + + private func updateUI() { + addLinkView.clearButton.isHidden = viewModel.outputs.isClearButtonHidden + addLinkView.nextTopButton.isEnabled = viewModel.outputs.isNextButtonEnabled + addLinkView.nextTopButton.backgroundColor = viewModel.outputs.nextButtonBackgroundColor + addLinkView.nextBottomButton.isEnabled = viewModel.outputs.isNextButtonEnabled + addLinkView.nextBottomButton.backgroundColor = viewModel.outputs.nextButtonBackgroundColor + addLinkView.linkEmbedTextField.layer.borderColor = viewModel.outputs.textFieldBorderColor.cgColor + addLinkView.linkEmbedTextField.layer.borderWidth = 1 + + if let errorMessage = viewModel.outputs.linkEffectivenessMessage { + addLinkView.isValidLinkError(errorMessage) } else { - let selectClipViewController = SelectClipViewController() - selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? "" - selectClipViewController.delegate = self - self.navigationController?.pushViewController(selectClipViewController, animated: true) + addLinkView.resetError() } } - } extension AddLinkViewController: SaveLinkButtonDelegate { From 1c7af6936b23e1c5d826613db81f8eb300bc1162 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 25 Sep 2024 09:22:20 +0900 Subject: [PATCH 08/98] =?UTF-8?q?[Chore]=20#201=20-=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddLink/LinkEmbed/View/AddLinkView.swift | 38 ++++-------------- .../View/AddLinkViewController.swift | 39 +++++++------------ 2 files changed, 21 insertions(+), 56 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift index 13524c0a..c965d2fe 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift @@ -12,9 +12,8 @@ import Then final class AddLinkView: UIView { - // MARK: - Properties - - private var timer: Timer? + // MARK: - Property + private var keyboardHeight: CGFloat = 100 // MARK: - UI Components @@ -36,7 +35,7 @@ final class AddLinkView: UIView { super.init(frame: frame) setLinkEmbedTextField() - setView() + setupView() } @available(*, unavailable) @@ -46,14 +45,13 @@ final class AddLinkView: UIView { // MARK: - Make View - func setView() { + func setupView() { setupStyle() setupHierarchy() setupLayout() } func setLinkEmbedTextField() { - linkEmbedTextField.delegate = self linkEmbedTextField.resignFirstResponder() } @@ -89,6 +87,7 @@ private extension AddLinkView { clearButton.do { $0.setImage(.icCancle24, for: .normal) $0.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + $0.isHidden = true } nextBottomButton.do { @@ -112,7 +111,6 @@ private extension AddLinkView { func setupHierarchy() { addSubviews(descriptLabel, linkEmbedTextField, nextBottomButton, clearButton) - clearButton.isHidden = true accessoryView.addSubview(nextTopButton) } @@ -155,7 +153,8 @@ private extension AddLinkView { } } - @objc func cancelButtonTapped() { + @objc + func cancelButtonTapped() { linkEmbedTextField.text = "" linkEmbedTextField.becomeFirstResponder() } @@ -163,29 +162,6 @@ private extension AddLinkView { // MARK: - Extension -extension AddLinkView: UITextFieldDelegate { - -// // 링크를 입력하는 텍스트필드가 비어 있을 경우 error 처리 -// func emptyError() { -// linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor -// linkEmbedTextField.layer.borderWidth = 1 -// -// // Button 비활성화 -// nextTopButton.backgroundColor = .gray200 -// nextBottomButton.backgroundColor = .gray200 -// nextTopButton.isEnabled = false -// nextBottomButton.isEnabled = false -// -// errorLabel.text = "링크를 입력해주세요" -// addSubview(errorLabel) -// errorLabel.snp.makeConstraints { -// $0.top.equalTo(linkEmbedTextField.snp.bottom).offset(6) -// $0.leading.equalTo(linkEmbedTextField.snp.leading) -// } -// errorLabel.isHidden = false -// } -} - extension AddLinkView { func isValidLinkError(_ message: String) { errorLabel.text = message diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index cd1a449f..910115c8 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -30,7 +30,7 @@ final class AddLinkViewController: UIViewController { private weak var delegate: AddLinkViewControllerPopDelegate? private weak var urldelegate: SelectClipViewControllerDelegate? - // MARK: - UI Properties + // MARK: - UI Components private var addLinkView = AddLinkView() private var viewModel = AddLinkViewModel() @@ -41,7 +41,7 @@ final class AddLinkViewController: UIViewController { super.viewDidLoad() setupStyle() - setAddLinkVew() + setupAddLinkVew() hideKeyboard() setupBinding() @@ -60,19 +60,6 @@ final class AddLinkViewController: UIViewController { navigationBarHidden(forHidden: false) } - - // MARK: - set up Add Link View - - private func setAddLinkVew() { - view.addSubview(addLinkView) - - addLinkView.snp.makeConstraints { - $0.edges.equalTo(view.safeAreaLayoutGuide) - } - - addLinkView.nextBottomButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside) - addLinkView.nextTopButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside) - } } // MARK: - extension @@ -96,6 +83,17 @@ private extension AddLinkViewController { view.backgroundColor = .toasterBackground } + func setupAddLinkVew() { + view.addSubview(addLinkView) + + addLinkView.snp.makeConstraints { + $0.edges.equalTo(view.safeAreaLayoutGuide) + } + + addLinkView.nextBottomButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside) + addLinkView.nextTopButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside) + } + func setupNavigationBar() { let type: ToasterNavigationType = ToasterNavigationType(hasBackButton: false, hasRightButton: true, @@ -127,24 +125,15 @@ private extension AddLinkViewController { } @objc func tappedNextBottomButton() { - let selectClipViewController = SelectClipViewController() selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? "" selectClipViewController.delegate = self self.navigationController?.pushViewController(selectClipViewController, animated: true) - -// if (addLinkView.linkEmbedTextField.text?.count ?? 0) < 1 { -// addLinkView.emptyError() -// } else { -// let selectClipViewController = SelectClipViewController() -// selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? "" -// selectClipViewController.delegate = self -// self.navigationController?.pushViewController(selectClipViewController, animated: true) -// } } } +// ViewModel extension AddLinkViewController { private func setupBinding() { addLinkView.linkEmbedTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) From f1c80c9916e387539aba22f43c481bca5b7649a4 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Fri, 27 Sep 2024 21:56:11 +0900 Subject: [PATCH 09/98] =?UTF-8?q?[Add]=20#201=20-=20ViewModelType=20protoc?= =?UTF-8?q?ol=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 ++++ TOASTER-iOS/Global/Protocols/ViewModelType.swift | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 TOASTER-iOS/Global/Protocols/ViewModelType.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 74f33752..ef9dbaa0 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -271,6 +271,7 @@ 8315CD8E2B547EE30061F377 /* SelectClipHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD8D2B547EE30061F377 /* SelectClipHeaderView.swift */; }; 8315CD912B5521F70061F377 /* SelectClipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD902B5521F70061F377 /* SelectClipModel.swift */; }; 832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */; }; + 8334CFA02CA6E2D200319922 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */; }; 83474A6A2BED06EB009B9C48 /* ToasterTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7B2B54571D008B06FA /* ToasterTargetType.swift */; }; 83474A6B2BED06F1009B9C48 /* PatchEditLinkTitleRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */; }; 83474A6C2BED072A009B9C48 /* ToasterAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7D2B54572B008B06FA /* ToasterAPIService.swift */; }; @@ -487,6 +488,7 @@ 8315CD8D2B547EE30061F377 /* SelectClipHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipHeaderView.swift; sourceTree = ""; }; 8315CD902B5521F70061F377 /* SelectClipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipModel.swift; sourceTree = ""; }; 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLinkViewModel.swift; sourceTree = ""; }; + 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; 8364220B2BE7BFB2005C4085 /* PatchEditLinkTitleResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleResponseDTO.swift; sourceTree = ""; }; 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleRequestDTO.swift; sourceTree = ""; }; 8388E98D2BC8FC6700858C5C /* EditLinkBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLinkBottomSheetView.swift; sourceTree = ""; }; @@ -941,6 +943,7 @@ isa = PBXGroup; children = ( 3F2FA1762B45C3E000EDBF95 /* AuthenticationAdapterProtocol.swift */, + 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */, ); path = Protocols; sourceTree = ""; @@ -1856,6 +1859,7 @@ 8305179D2B4D3701009FFB60 /* MainInfoModel.swift in Sources */, 6BE6DAA42B547579008B06FA /* GetDetailTimerResponseDTO.swift in Sources */, 6BE6D9E22B4E9B58008B06FA /* CompleteTimerCollectionViewCell.swift in Sources */, + 8334CFA02CA6E2D200319922 /* ViewModelType.swift in Sources */, 3F2FA1792B45C46F00EDBF95 /* KakaoAuthenticateAdapter.swift in Sources */, 6BE6DA342B50594B008B06FA /* MoyaPlugin.swift in Sources */, 6BE6D9D82B4E8A03008B06FA /* RemindAlarmOffViewType.swift in Sources */, diff --git a/TOASTER-iOS/Global/Protocols/ViewModelType.swift b/TOASTER-iOS/Global/Protocols/ViewModelType.swift new file mode 100644 index 00000000..fd8a6b53 --- /dev/null +++ b/TOASTER-iOS/Global/Protocols/ViewModelType.swift @@ -0,0 +1,15 @@ +// +// ViewModelType.swift +// TOASTER-iOS +// +// Created by Gahyun Kim on 9/27/24. +// + +import Foundation + +protocol ViewModelType { + associatedtype Input + associatedtype Output + + func transform(_ input: Input) -> Output +} From 382500a29e4a8e5edc7787a74a8ec662285d4274 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Fri, 27 Sep 2024 23:13:27 +0900 Subject: [PATCH 10/98] =?UTF-8?q?[Chore]=20#201=20-=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift | 2 +- .../Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift index c965d2fe..782b95c2 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift @@ -19,7 +19,7 @@ final class AddLinkView: UIView { // MARK: - UI Components private let descriptLabel = UILabel() - var linkEmbedTextField = UITextField() + private(set) var linkEmbedTextField = UITextField() let clearButton = UIButton() let nextBottomButton = UIButton() diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index ba68100f..dfd7c4b6 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -8,7 +8,8 @@ import UIKit protocol AddLinkViewModelInputs { - func embedLinkText(_ text: String)} + func embedLinkText(_ text: String) +} protocol AddLinkViewModelOutputs { var isClearButtonHidden: Bool { get } From a0cbeeeb6b8d4b2b909b2d451dc4a0cfa897c858 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 10:13:08 +0900 Subject: [PATCH 11/98] =?UTF-8?q?[Feat]=20#195=20-=20Combine=20=EA=B8=B0?= =?UTF-8?q?=EC=B4=88=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 81 ++++++++++++---- .../Extensions/Combine+/CancelBag.swift | 23 +++++ .../Combine+/Publisher+UIControl.swift | 61 ++++++++++++ .../Combine+/Publisher+UIGesture.swift | 95 +++++++++++++++++++ .../{ => Foundation+}/NSObject+.swift | 0 .../Extensions/{ => UIKit+}/UIColor+.swift | 0 .../Extensions/{ => UIKit+}/UILabel+.swift | 0 .../{ => UIKit+}/UIStackView+.swift | 0 .../{ => UIKit+}/UITextField+.swift | 0 .../Extensions/{ => UIKit+}/UIView+.swift | 0 .../{ => UIKit+}/UIViewController+.swift | 0 .../Global/Protocols/ViewModelType.swift | 3 +- 12 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 TOASTER-iOS/Global/Extensions/Combine+/CancelBag.swift create mode 100644 TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIControl.swift create mode 100644 TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift rename TOASTER-iOS/Global/Extensions/{ => Foundation+}/NSObject+.swift (100%) rename TOASTER-iOS/Global/Extensions/{ => UIKit+}/UIColor+.swift (100%) rename TOASTER-iOS/Global/Extensions/{ => UIKit+}/UILabel+.swift (100%) rename TOASTER-iOS/Global/Extensions/{ => UIKit+}/UIStackView+.swift (100%) rename TOASTER-iOS/Global/Extensions/{ => UIKit+}/UITextField+.swift (100%) rename TOASTER-iOS/Global/Extensions/{ => UIKit+}/UIView+.swift (100%) rename TOASTER-iOS/Global/Extensions/{ => UIKit+}/UIViewController+.swift (100%) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 2cbb47f4..6a77eea7 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ 3913B0B02BCECFC80031A3EB /* UpdateAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */; }; 391908422B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */; }; 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */; }; + 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7EC72C855F180034A14E /* ViewModelType.swift */; }; + 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */; }; + 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */; }; 396DCDF42CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDF32CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift */; }; 396DCDF52CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDF32CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift */; }; 396DCDF72CA19EFD00FEF7C8 /* PopupAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDF62CA19EFD00FEF7C8 /* PopupAPIService.swift */; }; @@ -34,9 +37,9 @@ 396DCE002CA19F5C00FEF7C8 /* GetPopupInfoResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDFF2CA19F5C00FEF7C8 /* GetPopupInfoResponseDTO.swift */; }; 396DCE012CA19F5C00FEF7C8 /* GetPopupInfoResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDFF2CA19F5C00FEF7C8 /* GetPopupInfoResponseDTO.swift */; }; 396DCE032CA26C6600FEF7C8 /* PopupInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCE022CA26C6600FEF7C8 /* PopupInfoModel.swift */; }; - 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7EC72C855F180034A14E /* ViewModelType.swift */; }; - 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */; }; - 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */; }; + 397215542CA8CF07009DF1F9 /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215532CA8CF07009DF1F9 /* CancelBag.swift */; }; + 397215592CA8D15F009DF1F9 /* Publisher+UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */; }; + 3972155B2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */; }; 398ACFDC2B5E77FA00D5EE77 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */; }; 398BE7F32B456367001595E0 /* ToasterToastMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */; }; 398BE7F62B456AF9001595E0 /* BottomType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F52B456AF9001595E0 /* BottomType.swift */; }; @@ -339,15 +342,18 @@ 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateAlertType.swift; sourceTree = ""; }; 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditPriorityCategoryRequestDTO.swift; sourceTree = ""; }; 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditNameCategoryRequestDTO.swift; sourceTree = ""; }; + 396D7EC72C855F180034A14E /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; + 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebViewModel.swift; sourceTree = ""; }; + 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebToolBarView.swift; sourceTree = ""; }; 396DCDF32CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchPopupHiddenResponseDTO.swift; sourceTree = ""; }; 396DCDF62CA19EFD00FEF7C8 /* PopupAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupAPIService.swift; sourceTree = ""; }; 396DCDF92CA19F2000FEF7C8 /* PopupTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupTargetType.swift; sourceTree = ""; }; 396DCDFC2CA19F4500FEF7C8 /* PatchPopupHiddenRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchPopupHiddenRequestDTO.swift; sourceTree = ""; }; 396DCDFF2CA19F5C00FEF7C8 /* GetPopupInfoResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPopupInfoResponseDTO.swift; sourceTree = ""; }; 396DCE022CA26C6600FEF7C8 /* PopupInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupInfoModel.swift; sourceTree = ""; }; - 396D7EC72C855F180034A14E /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; - 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebViewModel.swift; sourceTree = ""; }; - 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebToolBarView.swift; sourceTree = ""; }; + 397215532CA8CF07009DF1F9 /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; + 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIControl.swift"; sourceTree = ""; }; + 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIGesture.swift"; sourceTree = ""; }; 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterToastMessageView.swift; sourceTree = ""; }; 398BE7F52B456AF9001595E0 /* BottomType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomType.swift; sourceTree = ""; }; @@ -620,7 +626,7 @@ ); path = Model; sourceTree = ""; - }; + }; 3913B0AC2BCEC9A30031A3EB /* UpdateAlert */ = { isa = PBXGroup; children = ( @@ -630,6 +636,14 @@ path = UpdateAlert; sourceTree = ""; }; + 396D7EC92C855F4F0034A14E /* ViewModel */ = { + isa = PBXGroup; + children = ( + 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 396DCDE22CA199E800FEF7C8 /* Popup */ = { isa = PBXGroup; children = ( @@ -648,14 +662,45 @@ 396DCDF32CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift */, ); path = DTO; - sourceTree = ""; + sourceTree = ""; }; - 396D7EC92C855F4F0034A14E /* ViewModel */ = { + 397215502CA8CDFF009DF1F9 /* Recovered References */ = { isa = PBXGroup; children = ( - 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */, + 396D7EC72C855F180034A14E /* ViewModelType.swift */, ); - path = ViewModel; + name = "Recovered References"; + sourceTree = ""; + }; + 397215552CA8D0A1009DF1F9 /* UIKit+ */ = { + isa = PBXGroup; + children = ( + 6B6AE6AB2B3FF6F7000E2366 /* UIColor+.swift */, + 6B6AE6A92B3FF6EA000E2366 /* UIStackView+.swift */, + 6B6AE6A72B3FF6D5000E2366 /* UITextField+.swift */, + 6B6AE6A52B3FF6BD000E2366 /* UIView+.swift */, + 6B6AE6A32B3FF6B0000E2366 /* UIViewController+.swift */, + 830517AF2B4D9A3B009FFB60 /* UILabel+.swift */, + ); + path = "UIKit+"; + sourceTree = ""; + }; + 397215562CA8D0B1009DF1F9 /* Foundation+ */ = { + isa = PBXGroup; + children = ( + 39C3926A2B491F47005B2B0F /* NSObject+.swift */, + ); + path = "Foundation+"; + sourceTree = ""; + }; + 397215572CA8D0CA009DF1F9 /* Combine+ */ = { + isa = PBXGroup; + children = ( + 397215532CA8CF07009DF1F9 /* CancelBag.swift */, + 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */, + 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */, + ); + path = "Combine+"; sourceTree = ""; }; 398ACFDA2B5E77C500D5EE77 /* Assets */ = { @@ -891,6 +936,7 @@ 3FF2BF002BA17492001D7DC1 /* ToasterShareExtension */, 6B6AE64C2B3FF101000E2366 /* Products */, 390925C52B4EF66C00487AA3 /* Frameworks */, + 397215502CA8CDFF009DF1F9 /* Recovered References */, ); sourceTree = ""; }; @@ -961,13 +1007,9 @@ 6B6AE6812B3FF50A000E2366 /* Extensions */ = { isa = PBXGroup; children = ( - 6B6AE6AB2B3FF6F7000E2366 /* UIColor+.swift */, - 6B6AE6A92B3FF6EA000E2366 /* UIStackView+.swift */, - 6B6AE6A72B3FF6D5000E2366 /* UITextField+.swift */, - 6B6AE6A52B3FF6BD000E2366 /* UIView+.swift */, - 6B6AE6A32B3FF6B0000E2366 /* UIViewController+.swift */, - 39C3926A2B491F47005B2B0F /* NSObject+.swift */, - 830517AF2B4D9A3B009FFB60 /* UILabel+.swift */, + 397215572CA8D0CA009DF1F9 /* Combine+ */, + 397215552CA8D0A1009DF1F9 /* UIKit+ */, + 397215562CA8D0B1009DF1F9 /* Foundation+ */, ); path = Extensions; sourceTree = ""; @@ -1996,6 +2038,7 @@ 39B54E7A2B53D49900538DAE /* SettingTableViewCell.swift in Sources */, 39A843C52B736039007A4D75 /* ClipViewModel.swift in Sources */, 6BE6DA392B50636B008B06FA /* BaseAPIService.swift in Sources */, + 397215592CA8D15F009DF1F9 /* Publisher+UIControl.swift in Sources */, 6BE6DA572B50B44F008B06FA /* GetMyPageResponseDTO.swift in Sources */, 6BE6DA182B4FF285008B06FA /* TimerRepeatDate.swift in Sources */, 6BE6D9F22B4EEBC3008B06FA /* RemindViewModel.swift in Sources */, @@ -2007,6 +2050,7 @@ 6BC4935E2B4414B400544249 /* ToasterPopupViewController.swift in Sources */, 39BE4BBE2B4ABB7C002B471D /* DetailClipSegmentedControlView.swift in Sources */, 6BE6DA6D2B50C109008B06FA /* GetDetailCategoryResponseDTO.swift in Sources */, + 3972155B2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift in Sources */, 6B6AE6A82B3FF6D5000E2366 /* UITextField+.swift in Sources */, 6BE6DA652B50BA4F008B06FA /* PostAddCategoryRequestDTO.swift in Sources */, 6BE6D9DE2B4E9054008B06FA /* AlarmOffStateButton.swift in Sources */, @@ -2022,6 +2066,7 @@ 398BE7FE2B46C164001595E0 /* AddClipBottomSheetView.swift in Sources */, 6BE6DA012B4F2ACA008B06FA /* RemindSelectClipViewController.swift in Sources */, 6BE6DA592B50B45E008B06FA /* PatchPushAlarmResponseDTO.swift in Sources */, + 397215542CA8CF07009DF1F9 /* CancelBag.swift in Sources */, 39BE4BB62B4ABA97002B471D /* DetailClipListCollectionViewCell.swift in Sources */, 3FE28DC22B879B1400B6AED8 /* OnboardingType.swift in Sources */, 3F617CB52B4EC2EE00956E69 /* MypageHeaderView.swift in Sources */, diff --git a/TOASTER-iOS/Global/Extensions/Combine+/CancelBag.swift b/TOASTER-iOS/Global/Extensions/Combine+/CancelBag.swift new file mode 100644 index 00000000..25565268 --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/Combine+/CancelBag.swift @@ -0,0 +1,23 @@ +// +// CancelBag.swift +// TOASTER-iOS +// +// Created by 민 on 9/29/24. +// + +import Combine + +class CancelBag { + var cancellables = Set() + + deinit { + cancellables.forEach { $0.cancel() } + cancellables.removeAll() + } +} + +extension AnyCancellable { + func store(in cancelBag: CancelBag) { + cancelBag.cancellables.insert(self) + } +} diff --git a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIControl.swift b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIControl.swift new file mode 100644 index 00000000..3ceaa979 --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIControl.swift @@ -0,0 +1,61 @@ +// +// Publisher+UIControl.swift +// TOASTER-iOS +// +// Created by 민 on 9/29/24. +// + +import Combine +import UIKit + +extension UIControl { + func publisher(for events: UIControl.Event) -> UIControlPublisher { + UIControlPublisher(control: self, events: events) + } +} + +final class UIControlSubscription: Subscription where S.Input == Control { + private var subscriber: S? + private let control: Control + + init(subscriber: S, control: Control, event: UIControl.Event) { + self.subscriber = subscriber + self.control = control + control.addTarget(self, action: #selector(eventHandler), for: event) + } + + func request(_ demand: Subscribers.Demand) {} + + func cancel() { + subscriber = nil + } + + @objc + private func eventHandler() { + _ = subscriber?.receive(control) + } +} + +public struct UIControlPublisher: Publisher { + public typealias Output = Control + public typealias Failure = Never + + private let control: Control + private let controlEvents: UIControl.Event + + init(control: Control, events: UIControl.Event) { + self.control = control + self.controlEvents = events + } + + public func receive(subscriber: S) where S: Subscriber, + S.Failure == UIControlPublisher.Failure, + S.Input == UIControlPublisher.Output { + let subscription = UIControlSubscription( + subscriber: subscriber, + control: control, + event: controlEvents + ) + subscriber.receive(subscription: subscription) + } +} diff --git a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift new file mode 100644 index 00000000..c95ed1ca --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift @@ -0,0 +1,95 @@ +// +// Publisher+UIGesture.swift +// TOASTER-iOS +// +// Created by 민 on 9/29/24. +// + +import Combine +import UIKit + +extension UIView { + func gesture(_ gestureType: GestureType) -> GesturePublisher { + self.isUserInteractionEnabled = true + return GesturePublisher(view: self, gestureType: gestureType) + } +} + +final class GestureSubscription: Subscription where S.Input == GestureType { + private var subscriber: S? + private let gestureType: GestureType + private var view: UIView + + init(subscriber: S, gestureType: GestureType, view: UIView) { + self.subscriber = subscriber + self.gestureType = gestureType + self.view = view + configureGesture(gestureType) + } + + private func configureGesture(_ gestureType: GestureType) { + let gesture = gestureType.get() + gesture.addTarget(self, action: #selector(eventHandler)) + self.view.addGestureRecognizer(gesture) + } + + func request(_ demand: Subscribers.Demand) {} + + func cancel() { + subscriber = nil + } + + @objc + private func eventHandler() { + _ = subscriber?.receive(self.gestureType) + } +} + +public struct GesturePublisher: Publisher { + public typealias Output = GestureType + public typealias Failure = Never + + private let view: UIView + private let gestureType: GestureType + + init(view: UIView, gestureType: GestureType) { + self.view = view + self.gestureType = gestureType + } + + + public func receive(subscriber: S) where S: Subscriber, + S.Failure == GesturePublisher.Failure, + S.Input == GesturePublisher.Output { + let subscription = GestureSubscription( + subscriber: subscriber, + gestureType: self.gestureType, + view: self.view) + subscriber.receive(subscription: subscription) + } +} + +public enum GestureType { + case tap(UITapGestureRecognizer = .init()) + case swipe(UISwipeGestureRecognizer = .init()) + case longPress(UILongPressGestureRecognizer = .init()) + case pan(UIPanGestureRecognizer = .init()) + case pinch(UIPinchGestureRecognizer = .init()) + case edge(UIScreenEdgePanGestureRecognizer = .init()) + + func get() -> UIGestureRecognizer { + switch self { + case let .tap(tapGesture): return tapGesture + case let .swipe(swipeGesture): + return swipeGesture + case let .longPress(longPressGesture): + return longPressGesture + case let .pan(panGesture): + return panGesture + case let .pinch(pinchGesture): + return pinchGesture + case let .edge(edgePanGesture): + return edgePanGesture + } + } +} diff --git a/TOASTER-iOS/Global/Extensions/NSObject+.swift b/TOASTER-iOS/Global/Extensions/Foundation+/NSObject+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/NSObject+.swift rename to TOASTER-iOS/Global/Extensions/Foundation+/NSObject+.swift diff --git a/TOASTER-iOS/Global/Extensions/UIColor+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIColor+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/UIColor+.swift rename to TOASTER-iOS/Global/Extensions/UIKit+/UIColor+.swift diff --git a/TOASTER-iOS/Global/Extensions/UILabel+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UILabel+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/UILabel+.swift rename to TOASTER-iOS/Global/Extensions/UIKit+/UILabel+.swift diff --git a/TOASTER-iOS/Global/Extensions/UIStackView+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIStackView+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/UIStackView+.swift rename to TOASTER-iOS/Global/Extensions/UIKit+/UIStackView+.swift diff --git a/TOASTER-iOS/Global/Extensions/UITextField+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UITextField+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/UITextField+.swift rename to TOASTER-iOS/Global/Extensions/UIKit+/UITextField+.swift diff --git a/TOASTER-iOS/Global/Extensions/UIView+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIView+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/UIView+.swift rename to TOASTER-iOS/Global/Extensions/UIKit+/UIView+.swift diff --git a/TOASTER-iOS/Global/Extensions/UIViewController+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIViewController+.swift similarity index 100% rename from TOASTER-iOS/Global/Extensions/UIViewController+.swift rename to TOASTER-iOS/Global/Extensions/UIKit+/UIViewController+.swift diff --git a/TOASTER-iOS/Global/Protocols/ViewModelType.swift b/TOASTER-iOS/Global/Protocols/ViewModelType.swift index fd8a6b53..a58aaa74 100644 --- a/TOASTER-iOS/Global/Protocols/ViewModelType.swift +++ b/TOASTER-iOS/Global/Protocols/ViewModelType.swift @@ -5,11 +5,12 @@ // Created by Gahyun Kim on 9/27/24. // +import Combine import Foundation protocol ViewModelType { associatedtype Input associatedtype Output - func transform(_ input: Input) -> Output + func transform(_ input: Input, cancelBag: CancelBag) -> Output } From 2999b7d95820f05dcbcd79eac72a02e60121da3b Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 11:49:28 +0900 Subject: [PATCH 12/98] =?UTF-8?q?[Feat]=20#195=20-=20=EC=9B=B9=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=83=81=ED=83=9C=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=ED=88=B4=EB=B0=94=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift index 34579f14..b3905583 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -30,6 +30,7 @@ final class LinkWebToolBarView: UIView { private(set) var isRead: Bool = false { didSet { readLinkCheckButton.tintColor = isRead ? .gray700 : .gray150 + toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, safariButton], animated: false) } } @@ -133,7 +134,7 @@ private extension LinkWebToolBarView { func setupHierarchy() { addSubview(toolBar) - toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, safariButton], animated: false) + toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, safariButton], animated: false) } func setupLayout() { From c9c2c2ce96b399fa893fa40ef04cfb0d310c016d Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 13:34:33 +0900 Subject: [PATCH 13/98] =?UTF-8?q?[Feat]=20#195=20-=20UIBarButtonItem?= =?UTF-8?q?=EC=9A=A9=20Combine=20Publisher=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 6 +- .../Combine+/Publisher+UIBarButtonItem.swift | 58 +++++++++++++++++++ .../Combine+/Publisher+UIGesture.swift | 1 - 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIBarButtonItem.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 6a77eea7..4ee14731 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ 3913B0B02BCECFC80031A3EB /* UpdateAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */; }; 391908422B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */; }; 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */; }; - 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7EC72C855F180034A14E /* ViewModelType.swift */; }; 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */; }; 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */; }; 396DCDF42CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDF32CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift */; }; @@ -40,6 +39,7 @@ 397215542CA8CF07009DF1F9 /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215532CA8CF07009DF1F9 /* CancelBag.swift */; }; 397215592CA8D15F009DF1F9 /* Publisher+UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */; }; 3972155B2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */; }; + 3972155D2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */; }; 398ACFDC2B5E77FA00D5EE77 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */; }; 398BE7F32B456367001595E0 /* ToasterToastMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */; }; 398BE7F62B456AF9001595E0 /* BottomType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F52B456AF9001595E0 /* BottomType.swift */; }; @@ -354,6 +354,7 @@ 397215532CA8CF07009DF1F9 /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIControl.swift"; sourceTree = ""; }; 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIGesture.swift"; sourceTree = ""; }; + 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIBarButtonItem.swift"; sourceTree = ""; }; 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterToastMessageView.swift; sourceTree = ""; }; 398BE7F52B456AF9001595E0 /* BottomType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomType.swift; sourceTree = ""; }; @@ -699,6 +700,7 @@ 397215532CA8CF07009DF1F9 /* CancelBag.swift */, 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */, 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */, + 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */, ); path = "Combine+"; sourceTree = ""; @@ -2023,7 +2025,6 @@ 6BE6DA512B50B309008B06FA /* PatchPushAlarmRequestDTO.swift in Sources */, 830517962B4D21BB009FFB60 /* CompositioinalFactory.swift in Sources */, 6BE6D9E82B4EA773008B06FA /* RemindCollectionFooterView.swift in Sources */, - 396D7EC82C855F180034A14E /* ViewModelType.swift in Sources */, 39A843CA2B74512B007A4D75 /* DetailClipViewModel.swift in Sources */, 83CFC3392B568BE700A2EB2B /* RecommendSiteModel.swift in Sources */, 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */, @@ -2123,6 +2124,7 @@ 398BE7FA2B467E4B001595E0 /* ClipListCollectionViewCell.swift in Sources */, 39A843D12B746420007A4D75 /* EditClipViewModel.swift in Sources */, 6BE6DA552B50B43E008B06FA /* GetSettingPageResponseDTO.swift in Sources */, + 3972155D2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift in Sources */, 830517B02B4D9A3B009FFB60 /* UILabel+.swift in Sources */, 390925C42B4EF64100487AA3 /* LinkWebViewController.swift in Sources */, 6B6AE6A42B3FF6B0000E2366 /* UIViewController+.swift in Sources */, diff --git a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIBarButtonItem.swift b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIBarButtonItem.swift new file mode 100644 index 00000000..92d6ef2f --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIBarButtonItem.swift @@ -0,0 +1,58 @@ +// +// Publisher+UIBarButtonItem.swift +// TOASTER-iOS +// +// Created by 민 on 9/29/24. +// + +import Combine +import UIKit + +extension UIBarButtonItem { + func publisher() -> UIBarButtonItemPublisher { + UIBarButtonItemPublisher(item: self) + } +} + +final class UIBarButtonItemSubscription: Subscription where S.Input == Item { + private var subscriber: S? + private let item: Item + + init(subscriber: S, item: Item) { + self.subscriber = subscriber + self.item = item + item.target = self + item.action = #selector(eventHandler) + } + + func request(_ demand: Subscribers.Demand) {} + + func cancel() { + subscriber = nil + } + + @objc + private func eventHandler() { + _ = subscriber?.receive(item) + } +} + +public struct UIBarButtonItemPublisher: Publisher { + public typealias Output = Item + public typealias Failure = Never + + private let item: Item + + init(item: Item) { + self.item = item + } + + public func receive(subscriber: S) where S: Subscriber, + S.Failure == Self.Failure, + S.Input == Self.Output { + let subscription = UIBarButtonItemSubscription( + subscriber: subscriber, + item: item) + subscriber.receive(subscription: subscription) + } +} diff --git a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift index c95ed1ca..c25381db 100644 --- a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift +++ b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIGesture.swift @@ -57,7 +57,6 @@ public struct GesturePublisher: Publisher { self.gestureType = gestureType } - public func receive(subscriber: S) where S: Subscriber, S.Failure == GesturePublisher.Failure, S.Input == GesturePublisher.Output { From 6669462e8a94d4c00317bcd9361f485446728871 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 14:30:16 +0900 Subject: [PATCH 14/98] =?UTF-8?q?[Refactor]=20#195=20-=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EC=97=B4=EB=9E=8C=20=EC=97=AC=EB=B6=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20ViewModel=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LinkWeb/View/LinkWebToolBarView.swift | 9 +-- .../LinkWebViewController.swift | 59 +++++++------- .../LinkWeb/ViewModel/LinkWebViewModel.swift | 77 +++++++++++++++---- 3 files changed, 93 insertions(+), 52 deletions(-) diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift index b3905583..7641507f 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -36,9 +36,10 @@ final class LinkWebToolBarView: UIView { private var backButtonAction: (() -> Void)? private var forwardButtonAction: (() -> Void)? - private var readLinkCheckButtonAction: (() -> Void)? private var safariButtonAction: (() -> Void)? + lazy var readLinkButtonTap = readLinkCheckButton.publisher().eraseToAnyPublisher() + // MARK: - UI Components private let toolBar = UIToolbar() @@ -77,10 +78,6 @@ extension LinkWebToolBarView { forwardButtonAction = action } - func readLinkCheckButtonTapped(_ action: @escaping () -> Void) { - readLinkCheckButtonAction = action - } - func safariButtonTapped(_ action: @escaping () -> Void) { safariButtonAction = action } @@ -157,8 +154,6 @@ private extension LinkWebToolBarView { backButtonAction?() case forwardButton: forwardButtonAction?() - case readLinkCheckButton: - readLinkCheckButtonAction?() case safariButton: safariButtonAction?() default: diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 73effb7d..e2208cf7 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -15,6 +15,9 @@ final class LinkWebViewController: UIViewController { // MARK: - Properties + private var viewModel = LinkWebViewModel() + private var cancelBag = CancelBag() + private var progressObservation: NSKeyValueObservation? private var toastId: Int? @@ -30,6 +33,7 @@ final class LinkWebViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + bindViewModels() setupStyle() setupHierarchy() setupLayout() @@ -77,6 +81,30 @@ extension LinkWebViewController { // MARK: - Private Extensions private extension LinkWebViewController { + func bindViewModels() { + let readLinkButtonTapped = toolBar.readLinkButtonTap + .map { _ in + LinkReadEditModel(toastId: self.toastId ?? 0, isRead: !self.toolBar.isRead) + } + .eraseToAnyPublisher() + + let input = LinkWebViewModel.Input( + readLinkButtonTapped: readLinkButtonTapped + ) + + let output = viewModel.transform(input, cancelBag: cancelBag) + + output.isRead + .sink { [weak self] isRead in + if isRead { + self?.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.completeReadLink) + } else { + self?.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.cancelReadLink) + } + self?.toolBar.updateIsRead(isRead) + }.store(in: cancelBag) + } + func setupStyle() { view.bringSubviewToFront(progressView) view.backgroundColor = .toasterWhite @@ -157,15 +185,6 @@ private extension LinkWebViewController { } } - /// 툴바 링크 확인완료 버튼 클릭 액션 클로저 - toolBar.readLinkCheckButtonTapped { - if let toastId = self.toastId { - self.patchOpenLinkAPI( - requestBody: LinkReadEditModel(toastId: toastId, isRead: !self.toolBar.isRead) - ) - } - } - /// 툴바 사파리 버튼 클릭 액션 클로저 toolBar.safariButtonTapped { if let url = self.webView.url { UIApplication.shared.open(url) } @@ -195,25 +214,3 @@ extension LinkWebViewController: WKNavigationDelegate { progressView.isHidden = false } } - -// MARK: - Network - -extension LinkWebViewController { - func patchOpenLinkAPI(requestBody: LinkReadEditModel) { - NetworkService.shared.toastService.patchOpenLink(requestBody: PatchOpenLinkRequestDTO(toastId: requestBody.toastId, - isRead: requestBody.isRead)) { result in - switch result { - case .success: - if !self.toolBar.isRead { - self.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.completeReadLink) - } else { - self.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.cancelReadLink) - } - self.toolBar.updateIsRead(!self.toolBar.isRead) - case .unAuthorized, .networkFail, .notFound: - self.changeViewController(viewController: LoginViewController()) - default: return - } - } - } -} diff --git a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift index 67840ac1..b0b993f6 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift @@ -5,26 +5,75 @@ // Created by 민 on 9/2/24. // -import Foundation +import Combine +import UIKit -enum LinkWebViewModelInput { +final class LinkWebViewModel: ViewModelType { -} - -enum LinkWebViewModelOutput { + private var cancelBag = CancelBag() -} - -final class LinkWebViewModel: ViewModelType { + // MARK: - Input State - typealias Input = LinkWebViewModelInput - typealias Output = LinkWebViewModelOutput + struct Input { + let readLinkButtonTapped: AnyPublisher + } - func transform(_ input: Input) -> Output { - switch input { - - } + // MARK: - Output State + + struct Output { + let isRead = PassthroughSubject() + } + + // MARK: - Method + + func transform(_ input: Input, cancelBag: CancelBag) -> Output { + let output = Output() + + input.readLinkButtonTapped + .flatMap { [weak self] model -> AnyPublisher in + guard let self = self else { + return Just(false) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + return self.patchOpenLinkAPI(requestBody: model) + } + .sink( + receiveCompletion: { completion in + switch completion { + case .finished: break + case .failure(let error): print("Error occurred: \(error)") + } + }, + receiveValue: { isRead in + output.isRead.send(!isRead) + } + ).store(in: cancelBag) + + return output } } +// MARK: - Network +private extension LinkWebViewModel { + func patchOpenLinkAPI(requestBody: LinkReadEditModel) -> AnyPublisher { + return Future { promise in + NetworkService.shared.toastService.patchOpenLink( + requestBody: PatchOpenLinkRequestDTO( + toastId: requestBody.toastId, + isRead: requestBody.isRead + ) + ) { result in + switch result { + case .success: + promise(.success(!requestBody.isRead)) + case .unAuthorized, .networkFail, .notFound: + break + // self.changeViewController(viewController: LoginViewController()) + default: return + } + } + }.eraseToAnyPublisher() + } +} From e95469c446af5b4677f90ab75be52c7e9f5e7f39 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 14:53:39 +0900 Subject: [PATCH 15/98] =?UTF-8?q?[Refactor]=20#195=20-=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=91=9C?= =?UTF-8?q?=EC=B6=9C=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LinkWeb/ViewController/LinkWebViewController.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index e2208cf7..c5406757 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -96,11 +96,8 @@ private extension LinkWebViewController { output.isRead .sink { [weak self] isRead in - if isRead { - self?.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.completeReadLink) - } else { - self?.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.cancelReadLink) - } + let mesage = isRead ? StringLiterals.ToastMessage.completeReadLink : StringLiterals.ToastMessage.cancelReadLink + self?.showToastMessage(width: 152, status: .check, message: mesage) self?.toolBar.updateIsRead(isRead) }.store(in: cancelBag) } From 4a73fccde986650044b23e45da7e162300c05e4d Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 15:07:06 +0900 Subject: [PATCH 16/98] =?UTF-8?q?[Refactor]=20#195=20-=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EB=A7=8C=EB=A3=8C=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B7=B0=20=EC=A0=84=ED=99=98=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LinkWeb/ViewController/LinkWebViewController.swift | 5 +++++ TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index c5406757..013a2096 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -100,6 +100,11 @@ private extension LinkWebViewController { self?.showToastMessage(width: 152, status: .check, message: mesage) self?.toolBar.updateIsRead(isRead) }.store(in: cancelBag) + + output.navigateToLogin + .sink { [weak self] _ in + self?.changeViewController(viewController: LoginViewController()) + }.store(in: cancelBag) } func setupStyle() { diff --git a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift index b0b993f6..2aeb8b3c 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift @@ -22,6 +22,7 @@ final class LinkWebViewModel: ViewModelType { struct Output { let isRead = PassthroughSubject() + let navigateToLogin = PassthroughSubject() } // MARK: - Method @@ -69,8 +70,8 @@ private extension LinkWebViewModel { case .success: promise(.success(!requestBody.isRead)) case .unAuthorized, .networkFail, .notFound: - break - // self.changeViewController(viewController: LoginViewController()) + let output = Output() + output.navigateToLogin.send() default: return } } From 5b6706b1f9803ec7d9efa803311099e5eae86e7c Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 29 Sep 2024 17:04:02 +0900 Subject: [PATCH 17/98] =?UTF-8?q?[Chore]=20#195=20-=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LinkWebViewController.swift | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 013a2096..2938b93a 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -61,21 +61,15 @@ final class LinkWebViewController: UIViewController { // MARK: - Extensions extension LinkWebViewController { - func setupDataBind(linkURL: String, isRead: Bool, id: Int) { + func setupDataBind(linkURL: String, isRead: Bool? = nil, id: Int? = nil) { if let url = URL(string: linkURL) { let request = URLRequest(url: url) webView.load(request) } + guard let isRead, let id else { return } toolBar.updateIsRead(isRead) self.toastId = id } - - func setupDataBind(linkURL: String) { - if let url = URL(string: linkURL) { - let request = URLRequest(url: url) - webView.load(request) - } - } } // MARK: - Private Extensions @@ -165,26 +159,18 @@ private extension LinkWebViewController { } /// 네비게이션바 새로고침 버튼 클릭 액션 클로저 - navigationView.reloadButtonTapped { - self.webView.reload() - } + navigationView.reloadButtonTapped { self.webView.reload() } } func setupToolBarAction() { /// 툴바 뒤로가기 버튼 클릭 액션 클로저 toolBar.backButtonTapped { - if self.webView.canGoBack { - self.webView.goBack() - self.toolBar.updateCanGoBack(self.webView.canGoBack) - } + if self.webView.canGoBack { self.webView.goBack() } } /// 툴바 앞으로가기 버튼 클릭 액션 클로저 toolBar.forwardButtonTapped { - if self.webView.canGoForward { - self.webView.goForward() - self.toolBar.updateCanGoForward(self.webView.canGoForward) - } + if self.webView.canGoForward { self.webView.goForward() } } /// 툴바 사파리 버튼 클릭 액션 클로저 From f113f46c418ea350e997a16896223b49545d5b59 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Sun, 29 Sep 2024 21:35:46 +0900 Subject: [PATCH 18/98] =?UTF-8?q?[Chore]=20#207=20-=20Setting=20View,=20Vi?= =?UTF-8?q?ewController=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 + TOASTER-iOS/Present/Setting/SettingView.swift | 127 ++++++++++++++ .../Setting/SettingViewController.swift | 163 ++++++++++-------- 3 files changed, 222 insertions(+), 72 deletions(-) create mode 100644 TOASTER-iOS/Present/Setting/SettingView.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index aa909ca9..9d11f687 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -283,6 +283,7 @@ 8315CD912B5521F70061F377 /* SelectClipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD902B5521F70061F377 /* SelectClipModel.swift */; }; 832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */; }; 8334CFA02CA6E2D200319922 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */; }; + 8334CFA22CA979E700319922 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CFA12CA979E700319922 /* SettingView.swift */; }; 83474A6A2BED06EB009B9C48 /* ToasterTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7B2B54571D008B06FA /* ToasterTargetType.swift */; }; 83474A6B2BED06F1009B9C48 /* PatchEditLinkTitleRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */; }; 83474A6C2BED072A009B9C48 /* ToasterAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7D2B54572B008B06FA /* ToasterAPIService.swift */; }; @@ -506,6 +507,7 @@ 8315CD902B5521F70061F377 /* SelectClipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipModel.swift; sourceTree = ""; }; 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLinkViewModel.swift; sourceTree = ""; }; 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; + 8334CFA12CA979E700319922 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; 8364220B2BE7BFB2005C4085 /* PatchEditLinkTitleResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleResponseDTO.swift; sourceTree = ""; }; 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleRequestDTO.swift; sourceTree = ""; }; 8388E98D2BC8FC6700858C5C /* EditLinkBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLinkBottomSheetView.swift; sourceTree = ""; }; @@ -754,6 +756,7 @@ children = ( 39B54E722B53C50300538DAE /* SettingViewController.swift */, 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */, + 8334CFA12CA979E700319922 /* SettingView.swift */, ); path = Setting; sourceTree = ""; @@ -1927,6 +1930,7 @@ 83CFC3372B564F1100A2EB2B /* WeeklyLinkModel.swift in Sources */, 398BE7FC2B468F80001595E0 /* ClipCollectionHeaderView.swift in Sources */, 3F617CB82B4ECB6000956E69 /* MypageUserModel.swift in Sources */, + 8334CFA22CA979E700319922 /* SettingView.swift in Sources */, 6BE6DA612B50B742008B06FA /* ClipAPIService.swift in Sources */, 830517AA2B4D95E9009FFB60 /* HomeFooterCollectionView.swift in Sources */, 832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */, diff --git a/TOASTER-iOS/Present/Setting/SettingView.swift b/TOASTER-iOS/Present/Setting/SettingView.swift new file mode 100644 index 00000000..ed0b7079 --- /dev/null +++ b/TOASTER-iOS/Present/Setting/SettingView.swift @@ -0,0 +1,127 @@ +// +// SettingView.swift +// TOASTER-iOS +// +// Created by Gahyun Kim on 9/29/24. +// + +import UIKit + +import Kingfisher +import Then +import SnapKit + +final class SettingView: UIView { + + let settingList = ["알림 설정", "1:1 문의", "이용약관", "로그아웃"] + + // MARK: - UI Properties + + let alertWarningView = UIView() + private let warningStackView = UIStackView() + private let warningImage = UIImageView() + private let warningLabel = UILabel() + let settingTableView = UITableView(frame: .zero, style: .grouped) + + // MARK: - Life Cycle + + override init(frame: CGRect) { + super.init(frame: frame) + + setupStyle() + setupHierarchy() + setupLayout() +// setupWarningView() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + + +private extension SettingView { + func setupStyle() { + self.backgroundColor = .toasterBackground + + alertWarningView.do { + $0.backgroundColor = .gray50 + $0.makeRounded(radius: 12) + } + + warningStackView.do { + $0.spacing = 5 + } + + warningImage.do { + $0.image = .icAlert18Dark + $0.contentMode = .scaleAspectFit + } + + warningLabel.do { + $0.text = "알림 설정을 끄면 타이머 기능을 이용할 수 없어요" + $0.font = .suitBold(size: 12) + $0.textColor = .gray400 + } + + settingTableView.do { + $0.backgroundColor = .toasterBackground + $0.isScrollEnabled = false + $0.separatorStyle = .none + $0.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.className) +// $0.dataSource = self +// $0.delegate = self + } + } + + func setupHierarchy() { + addSubviews(alertWarningView, settingTableView) + alertWarningView.addSubview(warningStackView) + warningStackView.addArrangedSubviews(warningImage, warningLabel) + } + + func setupLayout() { + alertWarningView.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(42) + } + + warningStackView.snp.makeConstraints { + $0.center.equalToSuperview() + } + + warningImage.snp.makeConstraints { + $0.centerY.leading.equalToSuperview() + $0.size.equalTo(18) + } + + warningLabel.snp.makeConstraints { + $0.centerY.trailing.equalToSuperview() + } + + settingTableView.snp.makeConstraints { + $0.top.equalTo(alertWarningView.snp.bottom) + $0.leading.trailing.bottom.equalToSuperview() + } + } + +// func setupWarningView() { +// if let isToggle { +// if isToggle { +// settingTableView.snp.remakeConstraints { +// $0.top.equalTo(view.safeAreaLayoutGuide) +// $0.leading.trailing.bottom.equalToSuperview() +// } +// } else { +// settingTableView.snp.remakeConstraints { +// $0.top.equalTo(alertWarningView.snp.bottom) +// $0.leading.trailing.bottom.equalToSuperview() +// } +// } +// } +// } + +} diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index 176b5c70..56388385 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -14,10 +14,12 @@ final class SettingViewController: UIViewController { // MARK: - Properties - private let settingList = ["알림 설정", "1:1 문의", "이용약관", "로그아웃"] + private let rootView = SettingView() + +// private let settingList = ["알림 설정", "1:1 문의", "이용약관", "로그아웃"] private var isToggle: Bool? = UserDefaults.standard.object(forKey: "isAppAlarmOn") as? Bool { didSet { - settingTableView.reloadData() + rootView.settingTableView.reloadData() setupWarningView() UserDefaults.standard.set(isToggle, forKey: "isAppAlarmOn") } @@ -25,26 +27,29 @@ final class SettingViewController: UIViewController { private var userName: String = "" { didSet { - settingTableView.reloadData() + rootView.settingTableView.reloadData() } } - // MARK: - UI Properties + - private let alertWarningView = UIView() - private let warningStackView = UIStackView() - private let warningImage = UIImageView() - private let warningLabel = UILabel() - private let settingTableView = UITableView(frame: .zero, style: .grouped) + // MARK: - UI Properties +// +// private let alertWarningView = UIView() +// private let warningStackView = UIStackView() +// private let warningImage = UIImageView() +// private let warningLabel = UILabel() +// private let settingTableView = UITableView(frame: .zero, style: .grouped) // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() - setupStyle() +// setupStyle() setupHierarchy() setupLayout() + setupDelegate() setupWarningView() } @@ -58,71 +63,85 @@ final class SettingViewController: UIViewController { // MARK: - Private Extensions private extension SettingViewController { - func setupStyle() { - view.backgroundColor = .toasterBackground - - alertWarningView.do { - $0.backgroundColor = .gray50 - $0.makeRounded(radius: 12) - } - - warningStackView.do { - $0.spacing = 5 - } - - warningImage.do { - $0.image = .icAlert18Dark - $0.contentMode = .scaleAspectFit - } - - warningLabel.do { - $0.text = "알림 설정을 끄면 타이머 기능을 이용할 수 없어요" - $0.font = .suitBold(size: 12) - $0.textColor = .gray400 - } - - settingTableView.do { - $0.backgroundColor = .toasterBackground - $0.isScrollEnabled = false - $0.separatorStyle = .none - $0.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.className) - $0.dataSource = self - $0.delegate = self - } - } - func setupHierarchy() { - view.addSubviews(alertWarningView, settingTableView) - alertWarningView.addSubview(warningStackView) - warningStackView.addArrangedSubviews(warningImage, warningLabel) + view.addSubview(rootView) } func setupLayout() { - alertWarningView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide) - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(42) - } - - warningStackView.snp.makeConstraints { - $0.center.equalToSuperview() - } - - warningImage.snp.makeConstraints { - $0.centerY.leading.equalToSuperview() - $0.size.equalTo(18) - } - - warningLabel.snp.makeConstraints { - $0.centerY.trailing.equalToSuperview() - } - - settingTableView.snp.makeConstraints { - $0.top.equalTo(alertWarningView.snp.bottom) - $0.leading.trailing.bottom.equalToSuperview() + rootView.snp.makeConstraints { + $0.edges.equalTo(view.safeAreaLayoutGuide) } } +// func setupStyle() { +// view.backgroundColor = .toasterBackground +// +// alertWarningView.do { +// $0.backgroundColor = .gray50 +// $0.makeRounded(radius: 12) +// } +// +// warningStackView.do { +// $0.spacing = 5 +// } +// +// warningImage.do { +// $0.image = .icAlert18Dark +// $0.contentMode = .scaleAspectFit +// } +// +// warningLabel.do { +// $0.text = "알림 설정을 끄면 타이머 기능을 이용할 수 없어요" +// $0.font = .suitBold(size: 12) +// $0.textColor = .gray400 +// } +// +// settingTableView.do { +// $0.backgroundColor = .toasterBackground +// $0.isScrollEnabled = false +// $0.separatorStyle = .none +// $0.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.className) +// $0.dataSource = self +// $0.delegate = self +// } +// } +// +// func setupHierarchy() { +// view.addSubviews(alertWarningView, settingTableView) +// alertWarningView.addSubview(warningStackView) +// warningStackView.addArrangedSubviews(warningImage, warningLabel) +// } +// +// func setupLayout() { +// alertWarningView.snp.makeConstraints { +// $0.top.equalTo(view.safeAreaLayoutGuide) +// $0.leading.trailing.equalToSuperview().inset(20) +// $0.height.equalTo(42) +// } +// +// warningStackView.snp.makeConstraints { +// $0.center.equalToSuperview() +// } +// +// warningImage.snp.makeConstraints { +// $0.centerY.leading.equalToSuperview() +// $0.size.equalTo(18) +// } +// +// warningLabel.snp.makeConstraints { +// $0.centerY.trailing.equalToSuperview() +// } +// +// settingTableView.snp.makeConstraints { +// $0.top.equalTo(alertWarningView.snp.bottom) +// $0.leading.trailing.bottom.equalToSuperview() +// } +// } +// + func setupDelegate() { + rootView.settingTableView.dataSource = self + rootView.settingTableView.delegate = self + } func setupNavigationBar() { let type: ToasterNavigationType = ToasterNavigationType(hasBackButton: true, hasRightButton: false, @@ -138,13 +157,13 @@ private extension SettingViewController { func setupWarningView() { if let isToggle { if isToggle { - settingTableView.snp.remakeConstraints { + rootView.settingTableView.snp.remakeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) $0.leading.trailing.bottom.equalToSuperview() } } else { - settingTableView.snp.remakeConstraints { - $0.top.equalTo(alertWarningView.snp.bottom) + rootView.settingTableView.snp.remakeConstraints { + $0.top.equalTo(rootView.alertWarningView.snp.bottom) $0.leading.trailing.bottom.equalToSuperview() } } @@ -302,7 +321,7 @@ extension SettingViewController: UITableViewDataSource { case 0: cell.configureCell(name: userName, sectionNumber: indexPath.section) case 1: - cell.configureCell(name: settingList[indexPath.row], sectionNumber: indexPath.section) + cell.configureCell(name: rootView.settingList[indexPath.row], sectionNumber: indexPath.section) if indexPath.row == 0 { cell.showSwitch() cell.setSwitchValueChangedHandler { isOn in From 8f10f07bbdef39556b8d3486899de05f0177ab69 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Sun, 29 Sep 2024 21:49:33 +0900 Subject: [PATCH 19/98] =?UTF-8?q?[Chore]=20#207=20-=20rootView=20Layout=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 --- TOASTER-iOS/Present/Setting/SettingView.swift | 21 ----- .../Setting/SettingViewController.swift | 82 +------------------ 2 files changed, 3 insertions(+), 100 deletions(-) diff --git a/TOASTER-iOS/Present/Setting/SettingView.swift b/TOASTER-iOS/Present/Setting/SettingView.swift index ed0b7079..d8149f49 100644 --- a/TOASTER-iOS/Present/Setting/SettingView.swift +++ b/TOASTER-iOS/Present/Setting/SettingView.swift @@ -31,7 +31,6 @@ final class SettingView: UIView { setupStyle() setupHierarchy() setupLayout() -// setupWarningView() } @available(*, unavailable) @@ -41,7 +40,6 @@ final class SettingView: UIView { } - private extension SettingView { func setupStyle() { self.backgroundColor = .toasterBackground @@ -71,8 +69,6 @@ private extension SettingView { $0.isScrollEnabled = false $0.separatorStyle = .none $0.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.className) -// $0.dataSource = self -// $0.delegate = self } } @@ -107,21 +103,4 @@ private extension SettingView { $0.leading.trailing.bottom.equalToSuperview() } } - -// func setupWarningView() { -// if let isToggle { -// if isToggle { -// settingTableView.snp.remakeConstraints { -// $0.top.equalTo(view.safeAreaLayoutGuide) -// $0.leading.trailing.bottom.equalToSuperview() -// } -// } else { -// settingTableView.snp.remakeConstraints { -// $0.top.equalTo(alertWarningView.snp.bottom) -// $0.leading.trailing.bottom.equalToSuperview() -// } -// } -// } -// } - } diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index 56388385..8fdac5b7 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -15,8 +15,7 @@ final class SettingViewController: UIViewController { // MARK: - Properties private let rootView = SettingView() - -// private let settingList = ["알림 설정", "1:1 문의", "이용약관", "로그아웃"] + private var isToggle: Bool? = UserDefaults.standard.object(forKey: "isAppAlarmOn") as? Bool { didSet { rootView.settingTableView.reloadData() @@ -30,23 +29,12 @@ final class SettingViewController: UIViewController { rootView.settingTableView.reloadData() } } - - - // MARK: - UI Properties -// -// private let alertWarningView = UIView() -// private let warningStackView = UIStackView() -// private let warningImage = UIImageView() -// private let warningLabel = UILabel() -// private let settingTableView = UITableView(frame: .zero, style: .grouped) - // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() -// setupStyle() setupHierarchy() setupLayout() setupDelegate() @@ -69,79 +57,15 @@ private extension SettingViewController { func setupLayout() { rootView.snp.makeConstraints { - $0.edges.equalTo(view.safeAreaLayoutGuide) + $0.edges.equalToSuperview() } } -// func setupStyle() { -// view.backgroundColor = .toasterBackground -// -// alertWarningView.do { -// $0.backgroundColor = .gray50 -// $0.makeRounded(radius: 12) -// } -// -// warningStackView.do { -// $0.spacing = 5 -// } -// -// warningImage.do { -// $0.image = .icAlert18Dark -// $0.contentMode = .scaleAspectFit -// } -// -// warningLabel.do { -// $0.text = "알림 설정을 끄면 타이머 기능을 이용할 수 없어요" -// $0.font = .suitBold(size: 12) -// $0.textColor = .gray400 -// } -// -// settingTableView.do { -// $0.backgroundColor = .toasterBackground -// $0.isScrollEnabled = false -// $0.separatorStyle = .none -// $0.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.className) -// $0.dataSource = self -// $0.delegate = self -// } -// } -// -// func setupHierarchy() { -// view.addSubviews(alertWarningView, settingTableView) -// alertWarningView.addSubview(warningStackView) -// warningStackView.addArrangedSubviews(warningImage, warningLabel) -// } -// -// func setupLayout() { -// alertWarningView.snp.makeConstraints { -// $0.top.equalTo(view.safeAreaLayoutGuide) -// $0.leading.trailing.equalToSuperview().inset(20) -// $0.height.equalTo(42) -// } -// -// warningStackView.snp.makeConstraints { -// $0.center.equalToSuperview() -// } -// -// warningImage.snp.makeConstraints { -// $0.centerY.leading.equalToSuperview() -// $0.size.equalTo(18) -// } -// -// warningLabel.snp.makeConstraints { -// $0.centerY.trailing.equalToSuperview() -// } -// -// settingTableView.snp.makeConstraints { -// $0.top.equalTo(alertWarningView.snp.bottom) -// $0.leading.trailing.bottom.equalToSuperview() -// } -// } -// func setupDelegate() { rootView.settingTableView.dataSource = self rootView.settingTableView.delegate = self } + func setupNavigationBar() { let type: ToasterNavigationType = ToasterNavigationType(hasBackButton: true, hasRightButton: false, From 3ef0ee08a1e431dc10343e9ca844d4a1af4ad130 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 30 Sep 2024 00:44:58 +0900 Subject: [PATCH 20/98] =?UTF-8?q?[Feat]=20#207=20-=20MyPageHeader=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Present/Setting/SettingView.swift | 2 +- .../Setting/SettingViewController.swift | 62 +++++++++++++++---- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/TOASTER-iOS/Present/Setting/SettingView.swift b/TOASTER-iOS/Present/Setting/SettingView.swift index d8149f49..e3bee9a9 100644 --- a/TOASTER-iOS/Present/Setting/SettingView.swift +++ b/TOASTER-iOS/Present/Setting/SettingView.swift @@ -80,7 +80,7 @@ private extension SettingView { func setupLayout() { alertWarningView.snp.makeConstraints { - $0.top.equalTo(self.safeAreaLayoutGuide) + $0.top.equalToSuperview() $0.leading.trailing.equalToSuperview().inset(20) $0.height.equalTo(42) } diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index 8fdac5b7..0cc87324 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -14,11 +14,12 @@ final class SettingViewController: UIViewController { // MARK: - Properties - private let rootView = SettingView() + private let userInfoView = MypageHeaderView() + private let settingView = SettingView() private var isToggle: Bool? = UserDefaults.standard.object(forKey: "isAppAlarmOn") as? Bool { didSet { - rootView.settingTableView.reloadData() + settingView.settingTableView.reloadData() setupWarningView() UserDefaults.standard.set(isToggle, forKey: "isAppAlarmOn") } @@ -26,7 +27,7 @@ final class SettingViewController: UIViewController { private var userName: String = "" { didSet { - rootView.settingTableView.reloadData() + settingView.settingTableView.reloadData() } } @@ -35,6 +36,7 @@ final class SettingViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + setupStyle() setupHierarchy() setupLayout() setupDelegate() @@ -43,27 +45,39 @@ final class SettingViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + setupNavigationBar() fetchMysettings() + fetchMypageInformation() } } // MARK: - Private Extensions private extension SettingViewController { + func setupStyle() { + self.view.backgroundColor = .toasterBackground + } + func setupHierarchy() { - view.addSubview(rootView) + view.addSubviews(userInfoView, settingView) } func setupLayout() { - rootView.snp.makeConstraints { - $0.edges.equalToSuperview() + userInfoView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + $0.top.equalTo(view.safeAreaLayoutGuide) + } + + settingView.snp.makeConstraints { + $0.top.equalTo(userInfoView.weakLinkDataView.snp.bottom) + $0.horizontalEdges.bottom.equalToSuperview() } } func setupDelegate() { - rootView.settingTableView.dataSource = self - rootView.settingTableView.delegate = self + settingView.settingTableView.dataSource = self + settingView.settingTableView.delegate = self } func setupNavigationBar() { @@ -81,13 +95,13 @@ private extension SettingViewController { func setupWarningView() { if let isToggle { if isToggle { - rootView.settingTableView.snp.remakeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide) + settingView.settingTableView.snp.remakeConstraints { + $0.top.equalTo(userInfoView.weakLinkDataView.snp.bottom) $0.leading.trailing.bottom.equalToSuperview() } } else { - rootView.settingTableView.snp.remakeConstraints { - $0.top.equalTo(rootView.alertWarningView.snp.bottom) + settingView.settingTableView.snp.remakeConstraints { + $0.top.equalTo(settingView.alertWarningView.snp.bottom) $0.leading.trailing.bottom.equalToSuperview() } } @@ -159,6 +173,28 @@ private extension SettingViewController { } } + func fetchMypageInformation() { + NetworkService.shared.userService.getMyPage { [weak self] result in + switch result { + case .success(let response): + if let responseData = response?.data { + DispatchQueue.main.async { [weak self] in + self?.userInfoView.bindModel(model: MypageUserModel(nickname: responseData.nickname, + profile: responseData.profile, + allReadToast: responseData.allReadToast, + thisWeekendRead: responseData.thisWeekendRead, + thisWeekendSaved: responseData.thisWeekendSaved)) + } + } + case .unAuthorized, .networkFail: + self?.changeViewController(viewController: LoginViewController()) + default: + print("default Fail") + } + } + } + + func popupDeleteButtonTapped() { deleteAccount() } @@ -245,7 +281,7 @@ extension SettingViewController: UITableViewDataSource { case 0: cell.configureCell(name: userName, sectionNumber: indexPath.section) case 1: - cell.configureCell(name: rootView.settingList[indexPath.row], sectionNumber: indexPath.section) + cell.configureCell(name: settingView.settingList[indexPath.row], sectionNumber: indexPath.section) if indexPath.row == 0 { cell.showSwitch() cell.setSwitchValueChangedHandler { isOn in From 7dafcc808fd94bbb2002f2fa8a845467d5ad39e1 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 30 Sep 2024 01:13:45 +0900 Subject: [PATCH 21/98] =?UTF-8?q?[Feat]=20#207=20-=20section=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20isAppAlarmOn=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Setting/SettingTableViewCell.swift | 19 ++++++++++++------- .../Setting/SettingViewController.swift | 10 +++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/TOASTER-iOS/Present/Setting/SettingTableViewCell.swift b/TOASTER-iOS/Present/Setting/SettingTableViewCell.swift index 7c290bcd..b0514afe 100644 --- a/TOASTER-iOS/Present/Setting/SettingTableViewCell.swift +++ b/TOASTER-iOS/Present/Setting/SettingTableViewCell.swift @@ -43,18 +43,18 @@ extension SettingTableViewCell { func configureCell(name: String, sectionNumber: Int) { switch sectionNumber { case 0: - settingLabel.do { - $0.text = "\(name)님" - $0.font = .suitMedium(size: 18) - $0.asFont(targetString: name, font: .suitBold(size: 18)) - $0.textColor = .black900 - } - case 1: settingLabel.do { $0.text = name $0.font = .suitMedium(size: 18) $0.textColor = .black900 } + if name == "알림 설정" { + if let isOn = UserDefaults.standard.object(forKey: "isAppAlarmOn") as? Bool { + settingSwitch.isOn = isOn + } else { + settingSwitch.isOn = false + } + } default: settingLabel.do { $0.text = name @@ -68,6 +68,10 @@ extension SettingTableViewCell { settingSwitch.isHidden = false } + func hiddenSwitch() { + settingSwitch.isHidden = true + } + func setSwitchValueChangedHandler(_ handler: @escaping (Bool) -> Void) { switchValueChangedHandler = handler } @@ -111,5 +115,6 @@ private extension SettingTableViewCell { @objc func switchValueChanged(_ sender: UISwitch) { switchValueChangedHandler?(sender.isOn) + UserDefaults.standard.set(sender.isOn, forKey: "isAppAlarmOn") } } diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index 0cc87324..0b625995 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -166,6 +166,7 @@ private extension SettingViewController { switch result { case .success(let response): self.isToggle = response?.data?.isAllowed + self.setupWarningView() case .notFound, .networkFail: self.changeViewController(viewController: LoginViewController()) default: break @@ -260,14 +261,14 @@ extension SettingViewController: UITableViewDelegate { extension SettingViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - return 3 + return 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { +// case 0: +// return 1 case 0: - return 1 - case 1: return 4 default: return 1 @@ -279,8 +280,6 @@ extension SettingViewController: UITableViewDataSource { cell.selectionStyle = .none switch indexPath.section { case 0: - cell.configureCell(name: userName, sectionNumber: indexPath.section) - case 1: cell.configureCell(name: settingView.settingList[indexPath.row], sectionNumber: indexPath.section) if indexPath.row == 0 { cell.showSwitch() @@ -289,6 +288,7 @@ extension SettingViewController: UITableViewDataSource { } } default: + cell.hiddenSwitch() cell.configureCell(name: "탈퇴하기", sectionNumber: indexPath.section) } return cell From f260876bd335b751b9e49ab40a927421f2d1fd15 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 30 Sep 2024 10:13:13 +0900 Subject: [PATCH 22/98] =?UTF-8?q?[Feat]=20#195=20-=20eraseToAnyPublisher?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EB=B0=A9=EC=A7=80=EC=9A=A9=20=EC=9D=B5?= =?UTF-8?q?=EC=8A=A4=ED=85=90=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 +++ .../Combine+/Publisher+Driver.swift | 32 +++++++++++++++++++ .../LinkWeb/View/LinkWebToolBarView.swift | 2 +- .../LinkWeb/ViewModel/LinkWebViewModel.swift | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 TOASTER-iOS/Global/Extensions/Combine+/Publisher+Driver.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 4ee14731..4d51e89a 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 397215592CA8D15F009DF1F9 /* Publisher+UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */; }; 3972155B2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */; }; 3972155D2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */; }; + 397586EC2CAA312C004FB095 /* Publisher+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397586EB2CAA312C004FB095 /* Publisher+Driver.swift */; }; 398ACFDC2B5E77FA00D5EE77 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */; }; 398BE7F32B456367001595E0 /* ToasterToastMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */; }; 398BE7F62B456AF9001595E0 /* BottomType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F52B456AF9001595E0 /* BottomType.swift */; }; @@ -355,6 +356,7 @@ 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIControl.swift"; sourceTree = ""; }; 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIGesture.swift"; sourceTree = ""; }; 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIBarButtonItem.swift"; sourceTree = ""; }; + 397586EB2CAA312C004FB095 /* Publisher+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Driver.swift"; sourceTree = ""; }; 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 398BE7F22B456367001595E0 /* ToasterToastMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterToastMessageView.swift; sourceTree = ""; }; 398BE7F52B456AF9001595E0 /* BottomType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomType.swift; sourceTree = ""; }; @@ -698,6 +700,7 @@ isa = PBXGroup; children = ( 397215532CA8CF07009DF1F9 /* CancelBag.swift */, + 397586EB2CAA312C004FB095 /* Publisher+Driver.swift */, 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */, 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */, 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */, @@ -2138,6 +2141,7 @@ 6BE6DA072B4F2FC5008B06FA /* RemindClipModel.swift in Sources */, 6B6AE69F2B3FF5D7000E2366 /* LoginViewController.swift in Sources */, 6BE6DA982B54709D008B06FA /* PatchEditTimerTitleRequestDTO.swift in Sources */, + 397586EC2CAA312C004FB095 /* Publisher+Driver.swift in Sources */, 6BE6DA362B5059CE008B06FA /* NetworkResult.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+Driver.swift b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+Driver.swift new file mode 100644 index 00000000..bae8e2fe --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+Driver.swift @@ -0,0 +1,32 @@ +// +// Publisher+Driver.swift +// TOASTER-iOS +// +// Created by 민 on 9/30/24. +// + +import Combine +import Foundation + +public typealias Driver = AnyPublisher + +public extension Publisher { + func asDriver() -> Driver { + return self.catch { _ in Empty() } + .receive(on: RunLoop.main) + .eraseToAnyPublisher() + } + + static func just(_ output: Output) -> Driver { + return Just(output).eraseToAnyPublisher() + } + + static func empty() -> Driver { + return Empty().eraseToAnyPublisher() + } + + func mapVoid() -> AnyPublisher { + return self.map { _ in () } + .eraseToAnyPublisher() + } +} diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift index 7641507f..3cc727fa 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -38,7 +38,7 @@ final class LinkWebToolBarView: UIView { private var forwardButtonAction: (() -> Void)? private var safariButtonAction: (() -> Void)? - lazy var readLinkButtonTap = readLinkCheckButton.publisher().eraseToAnyPublisher() + lazy var readLinkButtonTap = readLinkCheckButton.publisher() // MARK: - UI Components diff --git a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift index 2aeb8b3c..f8a43c8e 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift @@ -15,7 +15,7 @@ final class LinkWebViewModel: ViewModelType { // MARK: - Input State struct Input { - let readLinkButtonTapped: AnyPublisher + let readLinkButtonTapped: Driver } // MARK: - Output State From 644b52bc21e8e5059811cb98224b897d2ec1a64b Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 30 Sep 2024 13:39:48 +0900 Subject: [PATCH 23/98] =?UTF-8?q?[Refactor]=20#195=20-=20readLinkButtonTap?= =?UTF-8?q?ped=20Combine=20=EA=B5=AC=ED=98=84=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Network/Base/NetworkResult.swift | 2 +- .../LinkWeb/ViewModel/LinkWebViewModel.swift | 38 +++++++++---------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/TOASTER-iOS/Network/Base/NetworkResult.swift b/TOASTER-iOS/Network/Base/NetworkResult.swift index 87bd9b5c..c839dad6 100644 --- a/TOASTER-iOS/Network/Base/NetworkResult.swift +++ b/TOASTER-iOS/Network/Base/NetworkResult.swift @@ -7,7 +7,7 @@ import Foundation -enum NetworkResult { +enum NetworkResult: Error { case success(T?) diff --git a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift index f8a43c8e..90aeced3 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewModel/LinkWebViewModel.swift @@ -31,28 +31,24 @@ final class LinkWebViewModel: ViewModelType { let output = Output() input.readLinkButtonTapped - .flatMap { [weak self] model -> AnyPublisher in - guard let self = self else { - return Just(false) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - return self.patchOpenLinkAPI(requestBody: model) + .flatMap { [weak self] model in + self?.performLinkReadRequest(model, output) ?? Driver.empty() } - .sink( - receiveCompletion: { completion in - switch completion { - case .finished: break - case .failure(let error): print("Error occurred: \(error)") - } - }, - receiveValue: { isRead in - output.isRead.send(!isRead) - } - ).store(in: cancelBag) + .sink { isRead in + output.isRead.send(!isRead) + }.store(in: cancelBag) return output } + + func performLinkReadRequest(_ model: LinkReadEditModel, _ output: Output) -> Driver { + return patchOpenLinkAPI(requestBody: model) + .handleEvents(receiveCompletion: { completion in + if case .failure = completion { + output.navigateToLogin.send() + } + }).asDriver() + } } // MARK: - Network @@ -70,9 +66,9 @@ private extension LinkWebViewModel { case .success: promise(.success(!requestBody.isRead)) case .unAuthorized, .networkFail, .notFound: - let output = Output() - output.navigateToLogin.send() - default: return + promise(.failure(NetworkResult.unAuthorized)) + default: + break } } }.eraseToAnyPublisher() From 27b4e8bb23435c3108ead7722856b27c3c6074d0 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 30 Sep 2024 14:19:44 +0900 Subject: [PATCH 24/98] =?UTF-8?q?[Feat]=20#208=20-=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20URL=20=EB=B3=B5=EC=82=AC=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift index b50b3a64..7b2bde70 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift @@ -20,7 +20,7 @@ final class LinkWebNavigationView: UIView { // MARK: - UI Components private let popButton = UIButton() - private let addressLabel = UILabel() + private let addressLabel = UITextField() private let reloadButton = UIButton() // MARK: - Life Cycles @@ -71,6 +71,7 @@ private extension LinkWebNavigationView { $0.font = .suitSemiBold(size: 14) $0.textColor = .black900 $0.textAlignment = .center + $0.inputView = UIView() } reloadButton.do { @@ -95,6 +96,7 @@ private extension LinkWebNavigationView { $0.centerY.equalTo(popButton) $0.leading.equalTo(popButton.snp.trailing).offset(13) $0.trailing.equalTo(reloadButton.snp.leading).inset(-13) + $0.height.equalTo(36) } reloadButton.snp.makeConstraints { From 3252b1272a7a294b3dcf6d99bc34a5d8c68eeab3 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 1 Oct 2024 14:31:30 +0900 Subject: [PATCH 25/98] =?UTF-8?q?[Feat]=20#208=20-=20=EC=9B=B9=EB=B7=B0=20?= =?UTF-8?q?=ED=88=B4=EB=B0=94=20=EA=B3=B5=EC=9C=A0=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Icons_24/ic_share.imageset/Contents.json | 12 +++++++++ .../Icons_24/ic_share.imageset/ic_share.svg | 3 +++ .../LinkWeb/View/LinkWebToolBarView.swift | 26 ++++++++++++++----- .../LinkWebViewController.swift | 7 +++++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/Contents.json create mode 100644 TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/ic_share.svg diff --git a/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/Contents.json b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/Contents.json new file mode 100644 index 00000000..2d17bf8a --- /dev/null +++ b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_share.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/ic_share.svg b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/ic_share.svg new file mode 100644 index 00000000..6a32d58b --- /dev/null +++ b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_share.imageset/ic_share.svg @@ -0,0 +1,3 @@ + + + diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift index 3cc727fa..f30d1e2e 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -16,26 +16,27 @@ final class LinkWebToolBarView: UIView { private var canGoBack: Bool = false { didSet { backButton.isEnabled = canGoBack - backButton.tintColor = canGoBack ? .gray700 : .gray150 + backButton.tintColor = canGoBack ? .gray700 : .gray200 } } private var canGoForward: Bool = false { didSet { forwardButton.isEnabled = canGoForward - forwardButton.tintColor = canGoForward ? .gray700 : .gray150 + forwardButton.tintColor = canGoForward ? .gray700 : .gray200 } } private(set) var isRead: Bool = false { didSet { - readLinkCheckButton.tintColor = isRead ? .gray700 : .gray150 - toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, safariButton], animated: false) + readLinkCheckButton.tintColor = isRead ? .gray700 : .gray200 + toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, shareLinkButton, flexibleSpace, safariButton], animated: false) } } private var backButtonAction: (() -> Void)? private var forwardButtonAction: (() -> Void)? + private var shareButtonAction: (() -> Void)? private var safariButtonAction: (() -> Void)? lazy var readLinkButtonTap = readLinkCheckButton.publisher() @@ -48,6 +49,7 @@ final class LinkWebToolBarView: UIView { private let backButton = UIBarButtonItem() private let forwardButton = UIBarButtonItem() private let readLinkCheckButton = UIBarButtonItem() + private let shareLinkButton = UIBarButtonItem() private let safariButton = UIBarButtonItem() // MARK: - Life Cycles @@ -78,6 +80,10 @@ extension LinkWebToolBarView { forwardButtonAction = action } + func shareButtonTapped(_ action: @escaping () -> Void) { + shareButtonAction = action + } + func safariButtonTapped(_ action: @escaping () -> Void) { safariButtonAction = action } @@ -122,6 +128,12 @@ private extension LinkWebToolBarView { $0.style = .plain } + shareLinkButton.do { + $0.tintColor = .gray700 + $0.image = .icShare + $0.style = .plain + } + safariButton.do { $0.tintColor = .gray700 $0.image = .icSafari24 @@ -131,7 +143,7 @@ private extension LinkWebToolBarView { func setupHierarchy() { addSubview(toolBar) - toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, safariButton], animated: false) + toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, shareLinkButton, flexibleSpace, safariButton], animated: false) } func setupLayout() { @@ -141,7 +153,7 @@ private extension LinkWebToolBarView { } func setupAddTarget() { - [backButton, forwardButton, readLinkCheckButton, safariButton].forEach { + [backButton, forwardButton, readLinkCheckButton, shareLinkButton, safariButton].forEach { $0.target = self $0.action = #selector(barButtonTapped) } @@ -154,6 +166,8 @@ private extension LinkWebToolBarView { backButtonAction?() case forwardButton: forwardButtonAction?() + case shareLinkButton: + shareButtonAction?() case safariButton: safariButtonAction?() default: diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 2938b93a..1c2c797a 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -173,6 +173,13 @@ private extension LinkWebViewController { if self.webView.canGoForward { self.webView.goForward() } } + /// 툴바 공유 버튼 클릭 액션 클로저 + toolBar.shareButtonTapped { + guard let url = self.webView.url else { return } + let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) + self.present(activityViewController, animated: true, completion: nil) + } + /// 툴바 사파리 버튼 클릭 액션 클로저 toolBar.safariButtonTapped { if let url = self.webView.url { UIApplication.shared.open(url) } From 50d8893f30605ba0d0ba52cfa08ef8710454204a Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 1 Oct 2024 14:41:00 +0900 Subject: [PATCH 26/98] =?UTF-8?q?[Chore]=20#209=20-=20Share=20Extension=20?= =?UTF-8?q?=EC=9D=B8=ED=8F=AC=20=EA=B6=8C=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ToasterShareExtension/Info.plist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ToasterShareExtension/Info.plist b/ToasterShareExtension/Info.plist index e713c9c1..cdbc520a 100644 --- a/ToasterShareExtension/Info.plist +++ b/ToasterShareExtension/Info.plist @@ -29,8 +29,10 @@ NSExtensionActivationRule - NSExtensionActivationSupportsURLWithMaxCount + NSExtensionActivationSupportsWebURLWithMaxCount 1 + NSExtensionActivationSupportsText + NSExtensionPointIdentifier From 36c7296bebc7726ca8a5675627b2da2782debc73 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 1 Oct 2024 15:22:51 +0900 Subject: [PATCH 27/98] =?UTF-8?q?[Chore]=20#209=20-=20Text=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=B4=EC=84=9C=EB=8F=84=20=EB=8C=80?= =?UTF-8?q?=EC=9D=91=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=BD=94=EB=93=9C=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 --- ToasterShareExtension/ShareViewController.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 4124c169..336e3b9a 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -198,13 +198,16 @@ private extension ShareViewController { // 웹 사이트 URL 를 받아올 수 있는 메서드 func getUrl() { if let item = extensionContext?.inputItems.first as? NSExtensionItem, - let itemProvider = item.attachments?.first as? NSItemProvider, - itemProvider.hasItemConformingToTypeIdentifier("public.url") { - itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { [weak self] (url, error) in - if let shareURL = url as? URL { - self?.urlString = shareURL.absoluteString - } else { - print("Error loading URL: \(error?.localizedDescription ?? "")") + let itemProviders = item.attachments { + itemProviders.forEach { itemProvider in + if itemProvider.hasItemConformingToTypeIdentifier("public.url") { + itemProvider.loadItem(forTypeIdentifier: "public.url") { [weak self] (url, error) in + if let shareURL = url as? URL { + self?.urlString = shareURL.absoluteString + } else { + print("Error loading URL: \(error?.localizedDescription ?? "")") + } + } } } } From c8c15441e26cd2fd794b197410b28bc13627c6fd Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 1 Oct 2024 17:41:55 +0900 Subject: [PATCH 28/98] =?UTF-8?q?[Chore]=20#207=20-=20layout=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 --- TOASTER-iOS/Present/Setting/SettingView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/Setting/SettingView.swift b/TOASTER-iOS/Present/Setting/SettingView.swift index e3bee9a9..5b4d059f 100644 --- a/TOASTER-iOS/Present/Setting/SettingView.swift +++ b/TOASTER-iOS/Present/Setting/SettingView.swift @@ -80,7 +80,7 @@ private extension SettingView { func setupLayout() { alertWarningView.snp.makeConstraints { - $0.top.equalToSuperview() + $0.top.equalToSuperview().offset(22) $0.leading.trailing.equalToSuperview().inset(20) $0.height.equalTo(42) } From 088c7566f9ee384269c9d58b689f9b359cfedadf Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 1 Oct 2024 17:49:15 +0900 Subject: [PATCH 29/98] =?UTF-8?q?[Chore]=20#207=20-=20seperatorView=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/Mypage/View/MypageHeaderView.swift | 16 ++++++++++++++-- .../Present/Setting/SettingViewController.swift | 8 ++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/TOASTER-iOS/Present/Mypage/View/MypageHeaderView.swift b/TOASTER-iOS/Present/Mypage/View/MypageHeaderView.swift index 5ccf6ea3..4c966228 100644 --- a/TOASTER-iOS/Present/Mypage/View/MypageHeaderView.swift +++ b/TOASTER-iOS/Present/Mypage/View/MypageHeaderView.swift @@ -25,13 +25,15 @@ final class MypageHeaderView: UIView { private let readLinkCountLabel = UILabel() private let readLinkCountUnitLabel = UILabel() - private let weakLinkDataView = UIView() + let weakLinkDataView = UIView() private let weakLinkDivider = UIView() private let openLinkLabel = UILabel() private let saveLinkLabel = UILabel() private let thisWeakOpenLinkCountLabel = UILabel() private let thisWeakSaveLinkCountLabel = UILabel() + let seperatorView = UIView() + // MARK: - Life Cycles override init(frame: CGRect) { @@ -131,10 +133,14 @@ private extension MypageHeaderView { $0.text = "nn" } } + + seperatorView.do { + $0.backgroundColor = .gray50 + } } func setupHierarchy() { - addSubviews(profileImageView, subTitleStackView, weakLinkDataView, readLinkCountLabel, readLinkCountUnitLabel) + addSubviews(profileImageView, subTitleStackView, weakLinkDataView, readLinkCountLabel, readLinkCountUnitLabel, seperatorView) subTitleStackView.addArrangedSubviews(topSubTitleLabel, bottomSubTitleLabel) @@ -195,6 +201,12 @@ private extension MypageHeaderView { $0.top.equalToSuperview().offset(22) $0.trailing.equalToSuperview() } + + seperatorView.snp.makeConstraints { + $0.top.equalTo(weakLinkDataView.snp.bottom).offset(24) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(4) + } } func changeFontColor(text: String) -> NSAttributedString { diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index 0b625995..6b30c6e9 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -69,8 +69,12 @@ private extension SettingViewController { $0.top.equalTo(view.safeAreaLayoutGuide) } + userInfoView.seperatorView.snp.makeConstraints { + $0.horizontalEdges.equalTo(view.safeAreaLayoutGuide) + } + settingView.snp.makeConstraints { - $0.top.equalTo(userInfoView.weakLinkDataView.snp.bottom) + $0.top.equalTo(userInfoView.seperatorView.snp.bottom) $0.horizontalEdges.bottom.equalToSuperview() } } @@ -96,7 +100,7 @@ private extension SettingViewController { if let isToggle { if isToggle { settingView.settingTableView.snp.remakeConstraints { - $0.top.equalTo(userInfoView.weakLinkDataView.snp.bottom) + $0.top.equalTo(userInfoView.seperatorView.snp.bottom) $0.leading.trailing.bottom.equalToSuperview() } } else { From f78e797cf4012ff3cf9002d44d2a66a47d742f2a Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 1 Oct 2024 22:26:44 +0900 Subject: [PATCH 30/98] =?UTF-8?q?[Chore]=20#208=20-=20=ED=88=B4=EB=B0=94?= =?UTF-8?q?=20=EA=B3=B5=EC=9C=A0=EB=B2=84=ED=8A=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets.xcassets/Icons_24/ic_read.imageset/ic_read.svg | 2 +- TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_read.imageset/ic_read.svg b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_read.imageset/ic_read.svg index 729b26d3..26a27a41 100644 --- a/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_read.imageset/ic_read.svg +++ b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_read.imageset/ic_read.svg @@ -1,3 +1,3 @@ - + diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift index f30d1e2e..05336ad7 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -29,7 +29,7 @@ final class LinkWebToolBarView: UIView { private(set) var isRead: Bool = false { didSet { - readLinkCheckButton.tintColor = isRead ? .gray700 : .gray200 + readLinkCheckButton.tintColor = isRead ? .toasterPrimary : .gray200 toolBar.setItems([backButton, flexibleSpace, forwardButton, flexibleSpace, readLinkCheckButton, flexibleSpace, shareLinkButton, flexibleSpace, safariButton], animated: false) } } From d223726ce5c528f999c97357b2650b4a71e1a5c9 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 2 Oct 2024 15:31:37 +0900 Subject: [PATCH 31/98] =?UTF-8?q?[Delete]=20#207=20-=20MyPage=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20TabBarItem=20=EC=9E=84=EC=8B=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 --- TOASTER-iOS.xcodeproj/project.pbxproj | 8 -- .../Present/Mypage/MypageViewController.swift | 98 ------------------- .../Present/Mypage/View/MypageAlertView.swift | 80 --------------- .../Setting/SettingViewController.swift | 2 - TOASTER-iOS/Present/TabBar/TabBarItem.swift | 2 +- 5 files changed, 1 insertion(+), 189 deletions(-) delete mode 100644 TOASTER-iOS/Present/Mypage/MypageViewController.swift delete mode 100644 TOASTER-iOS/Present/Mypage/View/MypageAlertView.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 9d11f687..85043a71 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 390247AA2B58016C00F9A86A /* PatchOpenLinkRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390247A92B58016C00F9A86A /* PatchOpenLinkRequestDTO.swift */; }; - 390247AC2B58263C00F9A86A /* MypageAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390247AB2B58263C00F9A86A /* MypageAlertView.swift */; }; 39049C8D2B43EEF400C9196E /* ToastStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39049C8C2B43EEF400C9196E /* ToastStatus.swift */; }; 39049C8F2B43F70400C9196E /* ToasterBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39049C8E2B43F70400C9196E /* ToasterBottomSheetViewController.swift */; }; 390925C42B4EF64100487AA3 /* LinkWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390925C32B4EF64100487AA3 /* LinkWebViewController.swift */; }; @@ -181,7 +180,6 @@ 6B6AE6782B3FF46C000E2366 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 6B6AE6772B3FF46C000E2366 /* Moya */; }; 6B6AE67A2B3FF46C000E2366 /* ReactiveMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 6B6AE6792B3FF46C000E2366 /* ReactiveMoya */; }; 6B6AE67C2B3FF46C000E2366 /* RxMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 6B6AE67B2B3FF46C000E2366 /* RxMoya */; }; - 6B6AE68A2B3FF582000E2366 /* MypageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B6AE6892B3FF582000E2366 /* MypageViewController.swift */; }; 6B6AE68D2B3FF58D000E2366 /* RemindViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B6AE68C2B3FF58D000E2366 /* RemindViewController.swift */; }; 6B6AE6902B3FF59C000E2366 /* DetailClipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B6AE68F2B3FF59C000E2366 /* DetailClipViewController.swift */; }; 6B6AE6932B3FF5A9000E2366 /* ClipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B6AE6922B3FF5A9000E2366 /* ClipViewController.swift */; }; @@ -322,7 +320,6 @@ /* Begin PBXFileReference section */ 390247A92B58016C00F9A86A /* PatchOpenLinkRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchOpenLinkRequestDTO.swift; sourceTree = ""; }; - 390247AB2B58263C00F9A86A /* MypageAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MypageAlertView.swift; sourceTree = ""; }; 39049C8C2B43EEF400C9196E /* ToastStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastStatus.swift; sourceTree = ""; }; 39049C8E2B43F70400C9196E /* ToasterBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterBottomSheetViewController.swift; sourceTree = ""; }; 390925C32B4EF64100487AA3 /* LinkWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebViewController.swift; sourceTree = ""; }; @@ -405,7 +402,6 @@ 6B6AE6572B3FF103000E2366 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6B6AE65A2B3FF103000E2366 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 6B6AE65C2B3FF103000E2366 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6B6AE6892B3FF582000E2366 /* MypageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MypageViewController.swift; sourceTree = ""; }; 6B6AE68C2B3FF58D000E2366 /* RemindViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindViewController.swift; sourceTree = ""; }; 6B6AE68F2B3FF59C000E2366 /* DetailClipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipViewController.swift; sourceTree = ""; }; 6B6AE6922B3FF5A9000E2366 /* ClipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipViewController.swift; sourceTree = ""; }; @@ -830,7 +826,6 @@ isa = PBXGroup; children = ( 3F617CB42B4EC2EE00956E69 /* MypageHeaderView.swift */, - 390247AB2B58263C00F9A86A /* MypageAlertView.swift */, ); path = View; sourceTree = ""; @@ -1040,7 +1035,6 @@ 6B6AE6882B3FF575000E2366 /* Mypage */ = { isa = PBXGroup; children = ( - 6B6AE6892B3FF582000E2366 /* MypageViewController.swift */, 3F617CB62B4ECB4600956E69 /* Model */, 3F617CB32B4EC2B500956E69 /* View */, ); @@ -2000,7 +1994,6 @@ 390247AA2B58016C00F9A86A /* PatchOpenLinkRequestDTO.swift in Sources */, 8309F5882B8DCEAC00A1420A /* SelectClipViewModel.swift in Sources */, 39049C8F2B43F70400C9196E /* ToasterBottomSheetViewController.swift in Sources */, - 390247AC2B58263C00F9A86A /* MypageAlertView.swift in Sources */, 6BE6DAB12B547BE1008B06FA /* GetMainPageSearchResponseDTO.swift in Sources */, 8315CD862B517DBF0061F377 /* AddLinkView.swift in Sources */, 8315CD912B5521F70061F377 /* SelectClipModel.swift in Sources */, @@ -2070,7 +2063,6 @@ 8315CD8E2B547EE30061F377 /* SelectClipHeaderView.swift in Sources */, 6B6AE6A22B3FF5F7000E2366 /* TabBarController.swift in Sources */, 3F2BFAC92B40370D00DA76B7 /* SocialLoginButtonView.swift in Sources */, - 6B6AE68A2B3FF582000E2366 /* MypageViewController.swift in Sources */, 6BE6DA432B50A999008B06FA /* PostRefreshTokenResponseDTO.swift in Sources */, 830471622B889641005AEEB4 /* HomeViewModel.swift in Sources */, 6BE6DA072B4F2FC5008B06FA /* RemindClipModel.swift in Sources */, diff --git a/TOASTER-iOS/Present/Mypage/MypageViewController.swift b/TOASTER-iOS/Present/Mypage/MypageViewController.swift deleted file mode 100644 index 0aadbd09..00000000 --- a/TOASTER-iOS/Present/Mypage/MypageViewController.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// MypageViewController.swift -// TOASTER-iOS -// -// Created by 김다예 on 12/30/23. -// - -import UIKit - -import SnapKit -import Then - -final class MypageViewController: UIViewController { - - // MARK: - UI Properties - - private let mypageHeaderView = MypageHeaderView() - private let mypageAlertView = MypageAlertView() - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - - setupStyle() - setupHierarchy() - setupLayout() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupNavigationBar() - fetchMypageInformation() - } -} - -// MARK: - Private Extensions - -private extension MypageViewController { - func setupStyle() { - view.backgroundColor = .toasterBackground - } - - func setupHierarchy() { - view.addSubviews(mypageHeaderView, mypageAlertView) - } - - func setupLayout() { - mypageHeaderView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide) - $0.horizontalEdges.equalToSuperview().inset(20) - } - - mypageAlertView.snp.makeConstraints { - $0.top.equalTo(mypageHeaderView.snp.bottom).offset(48) - $0.horizontalEdges.equalToSuperview() - } - } - - func setupNavigationBar() { - let type: ToasterNavigationType = ToasterNavigationType(hasBackButton: false, - hasRightButton: true, - mainTitle: StringOrImageType.string(StringLiterals.Tabbar.my), - rightButton: StringOrImageType.image(.icSettings24), - rightButtonAction: settingButtonTapped) - - if let navigationController = navigationController as? ToasterNavigationController { - navigationController.setupNavigationBar(forType: type) - } - } - - func settingButtonTapped() { - let settingVC = SettingViewController() - settingVC.hidesBottomBarWhenPushed = true - navigationController?.pushViewController(settingVC, animated: true) - } - - func fetchMypageInformation() { - NetworkService.shared.userService.getMyPage { [weak self] result in - switch result { - case .success(let response): - if let responseData = response?.data { - DispatchQueue.main.async { [weak self] in - self?.mypageHeaderView.bindModel(model: MypageUserModel(nickname: responseData.nickname, - profile: responseData.profile, - allReadToast: responseData.allReadToast, - thisWeekendRead: responseData.thisWeekendRead, - thisWeekendSaved: responseData.thisWeekendSaved)) - } - } - case .unAuthorized, .networkFail: - self?.changeViewController(viewController: LoginViewController()) - default: - print("default Fail") - } - } - } -} diff --git a/TOASTER-iOS/Present/Mypage/View/MypageAlertView.swift b/TOASTER-iOS/Present/Mypage/View/MypageAlertView.swift deleted file mode 100644 index 092fd9dc..00000000 --- a/TOASTER-iOS/Present/Mypage/View/MypageAlertView.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// MypageAlertView.swift -// TOASTER-iOS -// -// Created by 민 on 1/18/24. -// - -import UIKit - -import SnapKit -import Then - -final class MypageAlertView: UIView { - - // MARK: - UI Components - - private let seperatorView = UIView() - private let alertImage = UIImageView() - private let alertMessage = UILabel() - - // MARK: - Life Cycles - - override init(frame: CGRect) { - super.init(frame: frame) - - setupStyle() - setupHierarchy() - setupLayout() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -// MARK: - Private Extensions - -private extension MypageAlertView { - func setupStyle() { - seperatorView.do { - $0.backgroundColor = .gray50 - } - - alertImage.do { - $0.image = .imgAlarm - $0.contentMode = .scaleAspectFit - } - - alertMessage.do { - $0.numberOfLines = 2 - $0.text = "아직 마이페이지는 추가 공사 중! \n 업데이트를 기다려주세요:)" - $0.textAlignment = .center - $0.textColor = .gray500 - $0.font = .suitRegular(size: 16) - } - } - - func setupHierarchy() { - addSubviews(seperatorView, alertImage, alertMessage) - } - - func setupLayout() { - seperatorView.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.height.equalTo(4) - } - - alertImage.snp.makeConstraints { - $0.top.equalTo(seperatorView.snp.bottom).offset(convertByHeightRatio(32)) - $0.centerX.equalToSuperview() - $0.size.equalTo(200) - } - - alertMessage.snp.makeConstraints { - $0.top.equalTo(alertImage.snp.bottom).offset(4) - $0.centerX.equalToSuperview() - } - } -} diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index 6b30c6e9..bf66f793 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -270,8 +270,6 @@ extension SettingViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { -// case 0: -// return 1 case 0: return 4 default: diff --git a/TOASTER-iOS/Present/TabBar/TabBarItem.swift b/TOASTER-iOS/Present/TabBar/TabBarItem.swift index 27d9dc7d..381162cd 100644 --- a/TOASTER-iOS/Present/TabBar/TabBarItem.swift +++ b/TOASTER-iOS/Present/TabBar/TabBarItem.swift @@ -61,7 +61,7 @@ enum TabBarItem: CaseIterable { case .clip: return ClipViewController() case .plus: return ViewController() case .timer: return RemindViewController() - case .my: return MypageViewController() + case .my: return ViewController() } } } From fbab7369b59b1ea049c5292c536da0c88be56612 Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 3 Oct 2024 00:36:10 +0900 Subject: [PATCH 32/98] =?UTF-8?q?[Feat]=20#205=20-=20ShareViewModel=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20ShareViewController=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Binding=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShareViewController.swift | 110 +++++++++--------- ToasterShareExtension/ShareViewModel.swift | 102 ++++++++++++++++ 2 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 ToasterShareExtension/ShareViewModel.swift diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 4124c169..7aad95a7 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -7,6 +7,7 @@ import UIKit import Social +import Combine import SnapKit import Then @@ -15,29 +16,27 @@ import Then class ShareViewController: UIViewController { // MARK: - Properties - private var urlString = "" - private let viewModel = RemindSelectClipViewModel() - private var categoryID: Int? - private var selectedClip: RemindClipModel? { - didSet { - nextBottomButton.backgroundColor = .toasterBlack - } - } + private let viewModel = RemindSelectClipViewModel() + private let shareViewModel = ShareViewModel() + private var titleHeight: Int { return isUseShareExtension ? 64 : 0 } - private let appURL = "TOASTER://" private var isUseShareExtension = false + private let selectedClipRelay = CurrentValueSubject(nil) + + private var cancellables = Set() + // MARK: - UI Components private let titleLabel = UILabel() private let closeButton = UIButton() private let bottomSheetView = UIView() private var clipSelectCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) - private let nextBottomButton = UIButton() + private let completeBottomButton = UIButton() // MARK: - Life Cycles @@ -49,10 +48,10 @@ class ShareViewController: UIViewController { setupHierarchy() setupLayout() setupDelegate() - setupButton() setupRegisterCell() setupViewModel() fetchCheckTokenHealth() + bindViewModel() } override func viewDidAppear(_ animated: Bool) { @@ -120,8 +119,8 @@ private extension ShareViewController { $0.showsVerticalScrollIndicator = false } - nextBottomButton.do { - $0.setTitle(StringLiterals.Button.next, for: .normal) + completeBottomButton.do { + $0.setTitle(StringLiterals.Button.complete, for: .normal) $0.setTitleColor(.toasterWhite, for: .normal) $0.backgroundColor = .gray200 $0.makeRounded(radius: 12) @@ -130,7 +129,7 @@ private extension ShareViewController { func setupHierarchy() { view.addSubviews(bottomSheetView) - bottomSheetView.addSubviews(titleLabel, closeButton, clipSelectCollectionView, nextBottomButton) + bottomSheetView.addSubviews(titleLabel, closeButton, clipSelectCollectionView, completeBottomButton) } func setupLayout() { @@ -150,10 +149,10 @@ private extension ShareViewController { clipSelectCollectionView.snp.makeConstraints { $0.top.equalTo(titleLabel.snp.bottom).offset(22) $0.horizontalEdges.equalToSuperview().inset(20) - $0.bottom.equalTo(nextBottomButton.snp.top).inset(-20) + $0.bottom.equalTo(completeBottomButton.snp.top).inset(-20) } - nextBottomButton.snp.makeConstraints { + completeBottomButton.snp.makeConstraints { $0.centerX.equalToSuperview() $0.bottom.equalToSuperview().inset(34) $0.horizontalEdges.equalToSuperview().inset(20) @@ -170,11 +169,6 @@ private extension ShareViewController { clipSelectCollectionView.dataSource = self } - func setupButton() { - closeButton.addTarget(self, action: #selector(hideBottomSheetAction), for: .touchUpInside) - nextBottomButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) - } - func setupViewModel() { viewModel.setupDataChangeAction(changeAction: reloadCollectionView) } @@ -182,19 +176,7 @@ private extension ShareViewController { func reloadCollectionView() { clipSelectCollectionView.reloadData() } - - @objc func hideBottomSheetAction(_ sender: UIButton) { - UIView.animate(withDuration: 0.3, animations: { - self.view.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height) - }, completion: { _ in - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) - }) - } - - @objc func nextButtonTapped(_ sender: UIButton) { - postSaveLink(url: urlString, category: categoryID) - } - + // 웹 사이트 URL 를 받아올 수 있는 메서드 func getUrl() { if let item = extensionContext?.inputItems.first as? NSExtensionItem, @@ -202,7 +184,7 @@ private extension ShareViewController { itemProvider.hasItemConformingToTypeIdentifier("public.url") { itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { [weak self] (url, error) in if let shareURL = url as? URL { - self?.urlString = shareURL.absoluteString + self?.shareViewModel.bindUrl(shareURL.absoluteString) } else { print("Error loading URL: \(error?.localizedDescription ?? "")") } @@ -213,7 +195,7 @@ private extension ShareViewController { func fetchCheckTokenHealth() { NetworkService.shared.authService.postTokenHealth(tokenType: .accessToken) { [weak self] result in switch result { - case .success(let response): + case .success(_): self?.isUseShareExtension = true case .unAuthorized, .networkFail: self?.isUseShareExtension = false @@ -225,22 +207,41 @@ private extension ShareViewController { } } - func postSaveLink(url: String, category: Int?) { - let request = PostSaveLinkRequestDTO(linkUrl: url, - categoryId: category) - NetworkService.shared.toastService.postSaveLink(requestBody: request) { [weak self] result in - switch result { - case .success: - print("저장 성공") - self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) - case .networkFail, .unAuthorized, .notFound: - print("저장 실패") - case .badRequest, .serverErr: - print("저장 실패") - default: - return + func bindViewModel() { + let input = ShareViewModel.Input( + selectedClip: selectedClipRelay.eraseToAnyPublisher(), + completeButtonTap: completeBottomButton.tapPublisher, + closeButtonTap: closeButton.tapPublisher + ) + + let output = shareViewModel.transform(input) + + output.isSeleted + .sink { [weak self] result in + if result == true { + self?.completeBottomButton.backgroundColor = .toasterBlack + } } - } + .store(in: &cancellables) + + output.completeButtonAction + .sink { [weak self] result in + if result == true { + self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + } + } + .store(in: &cancellables) + + output.closeButtonAction + .sink { [weak self] _ in + guard let self = self else { return } + UIView.animate(withDuration: 0.3, animations: { + self.view.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height) + }, completion: { _ in + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + }) + } + .store(in: &cancellables) } } @@ -270,7 +271,7 @@ private extension ShareViewController { func openMyApp() { self.extensionContext?.completeRequest(returningItems: nil, completionHandler: { _ in - guard let url = URL(string: self.appURL) else { return } + guard let url = URL(string: self.shareViewModel.readAppURL()) else { return } _ = self.openURL(url) }) } @@ -291,9 +292,8 @@ private extension ShareViewController { extension ShareViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - selectedClip = viewModel.clipData[indexPath.item] - let selectRow = viewModel.clipData[indexPath.item].id - categoryID = selectRow == 0 ? nil : selectRow + let selectedClip = viewModel.clipData[indexPath.item] + selectedClipRelay.send(selectedClip) } } diff --git a/ToasterShareExtension/ShareViewModel.swift b/ToasterShareExtension/ShareViewModel.swift new file mode 100644 index 00000000..b3ea9352 --- /dev/null +++ b/ToasterShareExtension/ShareViewModel.swift @@ -0,0 +1,102 @@ +// +// ShareViewModel.swift +// ToasterShareExtension +// +// Created by ParkJunHyuk on 9/29/24. +// + +import Foundation +import Combine + +protocol ViewModelType { + associatedtype Input + associatedtype Output + + func transform(_ input: Input) -> Output +} + +final class ShareViewModel: ViewModelType { + + private let appURL = "TOASTER://" + private var urlString = "" + + struct Input { + let selectedClip: AnyPublisher + let completeButtonTap: AnyPublisher + let closeButtonTap: AnyPublisher + } + + struct Output { + let isSeleted: AnyPublisher + let completeButtonAction: AnyPublisher + let closeButtonAction: AnyPublisher + } + + private var cancellables = Set() + + func transform(_ input: Input) -> Output { + let categoryIDPublisher = input.selectedClip + .map { clip -> Int? in + guard let clip = clip else { return nil } + return clip.id == 0 ? nil : clip.id + } + .eraseToAnyPublisher() + + let isSelectedPublisher = input.selectedClip + .map { $0 != nil } + .eraseToAnyPublisher() + + let saveLinkResultPublisher = input.completeButtonTap + .combineLatest(categoryIDPublisher) + .map { _, categoryID in categoryID } + .flatMap { [weak self] categoryID -> AnyPublisher in + guard let self = self else { + return Just(false).eraseToAnyPublisher() + } + + return self.postSaveLink(id: categoryID) + .catch { error -> AnyPublisher in + print("실패: \(error.localizedDescription)") + return Just(false).eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + + return Output( + isSeleted: isSelectedPublisher, + completeButtonAction: saveLinkResultPublisher, + closeButtonAction: input.closeButtonTap + ) + } + + func bindUrl(_ url: String) { + self.urlString = url + } + + func readAppURL() -> String { + return appURL + } +} + +// MARK: - API Methods + +private extension ShareViewModel { + func postSaveLink(id: Int?) -> AnyPublisher { + let request = PostSaveLinkRequestDTO(linkUrl: self.urlString, categoryId: id) + + return Future { promise in + NetworkService.shared.toastService.postSaveLink(requestBody: request) { result in + switch result { + case .success: + print("저장 성공") + promise(.success(true)) + case .networkFail, .unAuthorized, .notFound, .badRequest, .serverErr, .decodeErr, .unProcessable: + print("저장 실패") + promise(.failure(NSError(domain: "PostSaveLinkError", code: 0, userInfo: [NSLocalizedDescriptionKey: "링크 저장에 실패했습니다."]))) + } + } + } + .eraseToAnyPublisher() + } +} From 426dff84414e24ef365949b584fd38e6218ec516 Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 3 Oct 2024 00:39:23 +0900 Subject: [PATCH 33/98] =?UTF-8?q?[Feat]=20#205=20-=20pbxproj=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index aa909ca9..7a39ab98 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -142,6 +142,7 @@ 3F7D91812BA1A0D9004A022F /* SUIT-SemiBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B102B410EF2004024E6 /* SUIT-SemiBold.otf */; }; 3F7D91822BA1A0DC004A022F /* SUIT-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B122B410EF3004024E6 /* SUIT-Regular.otf */; }; 3F7D91832BA1A0DE004A022F /* SUIT-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B0F2B410EF2004024E6 /* SUIT-Medium.otf */; }; + 3F82C30F2CA92AAB00492EEE /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82C30E2CA92AAB00492EEE /* ShareViewModel.swift */; }; 3FA56CD72B85C76B00B9FCFE /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA56CD62B85C76B00B9FCFE /* OnboardingViewController.swift */; }; 3FA8654F2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA8654E2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift */; }; 3FACF9B82B4FE306007E5A8F /* KeyChainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FACF9B72B4FE306007E5A8F /* KeyChainService.swift */; }; @@ -384,6 +385,7 @@ 3F617CB72B4ECB6000956E69 /* MypageUserModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MypageUserModel.swift; sourceTree = ""; }; 3F6CD49C2B86229A00DEC113 /* CustomPageIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPageIndicatorView.swift; sourceTree = ""; }; 3F7D91712BA18C93004A022F /* ToasterShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ToasterShareExtension.entitlements; sourceTree = ""; }; + 3F82C30E2CA92AAB00492EEE /* ShareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = ""; }; 3FA56CD62B85C76B00B9FCFE /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 3FA8654E2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTokenHealthResponseDTO.swift; sourceTree = ""; }; 3FACF9B72B4FE306007E5A8F /* KeyChainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChainService.swift; sourceTree = ""; }; @@ -847,6 +849,7 @@ 3FF2BF062BA17492001D7DC1 /* Info.plist */, 3F1F26252BAAC231004F75CE /* ShareViewController.swift */, 3FE00F0A2BC6328900CC821E /* MoyaPlugin.swift */, + 3F82C30E2CA92AAB00492EEE /* ShareViewModel.swift */, ); path = ToasterShareExtension; sourceTree = ""; @@ -1828,6 +1831,7 @@ 3F1F261D2BAA98C8004F75CE /* RemindClipModel.swift in Sources */, 3F3ED28C2BA1A456004E79F0 /* PostRefreshTokenResponseDTO.swift in Sources */, 3F3ED28D2BA1A456004E79F0 /* AuthAPIService.swift in Sources */, + 3F82C30F2CA92AAB00492EEE /* ShareViewModel.swift in Sources */, 3F3ED2892BA1A453004E79F0 /* PostSocialLoginRequestDTO.swift in Sources */, 3F3ED27B2BA1A298004E79F0 /* BaseTargetType.swift in Sources */, 3F3ED28E2BA1A45C004E79F0 /* PatchPushAlarmRequestDTO.swift in Sources */, From e2b16c5c68c1b461b204637cc698328743eba943 Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 3 Oct 2024 00:48:48 +0900 Subject: [PATCH 34/98] =?UTF-8?q?[Feat]=20#205=20-=20UIButton=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=08Combine=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=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 --- TOASTER-iOS.xcodeproj/project.pbxproj | 16 ++++++++++++++++ .../Combine+/Publisher+UIButton.swift | 17 +++++++++++++++++ ToasterShareExtension/ShareViewController.swift | 6 +++--- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIButton.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index f3409f1a..5f13b41d 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -150,6 +150,13 @@ 3F7D91822BA1A0DC004A022F /* SUIT-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B122B410EF3004024E6 /* SUIT-Regular.otf */; }; 3F7D91832BA1A0DE004A022F /* SUIT-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B0F2B410EF2004024E6 /* SUIT-Medium.otf */; }; 3F82C30F2CA92AAB00492EEE /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82C30E2CA92AAB00492EEE /* ShareViewModel.swift */; }; + 3F82C3212CADA19300492EEE /* Publisher+UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82C3202CADA19300492EEE /* Publisher+UIButton.swift */; }; + 3F82C3222CADA1EF00492EEE /* Publisher+UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82C3202CADA19300492EEE /* Publisher+UIButton.swift */; }; + 3F82C3232CADA1F400492EEE /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215532CA8CF07009DF1F9 /* CancelBag.swift */; }; + 3F82C3242CADA1F700492EEE /* Publisher+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397586EB2CAA312C004FB095 /* Publisher+Driver.swift */; }; + 3F82C3252CADA1F900492EEE /* Publisher+UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */; }; + 3F82C3262CADA1FC00492EEE /* Publisher+UIGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */; }; + 3F82C3272CADA1FE00492EEE /* Publisher+UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */; }; 3FA56CD72B85C76B00B9FCFE /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA56CD62B85C76B00B9FCFE /* OnboardingViewController.swift */; }; 3FA8654F2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA8654E2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift */; }; 3FACF9B82B4FE306007E5A8F /* KeyChainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FACF9B72B4FE306007E5A8F /* KeyChainService.swift */; }; @@ -401,6 +408,7 @@ 3F6CD49C2B86229A00DEC113 /* CustomPageIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPageIndicatorView.swift; sourceTree = ""; }; 3F7D91712BA18C93004A022F /* ToasterShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ToasterShareExtension.entitlements; sourceTree = ""; }; 3F82C30E2CA92AAB00492EEE /* ShareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = ""; }; + 3F82C3202CADA19300492EEE /* Publisher+UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIButton.swift"; sourceTree = ""; }; 3FA56CD62B85C76B00B9FCFE /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 3FA8654E2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTokenHealthResponseDTO.swift; sourceTree = ""; }; 3FACF9B72B4FE306007E5A8F /* KeyChainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChainService.swift; sourceTree = ""; }; @@ -706,6 +714,7 @@ 397215582CA8D15F009DF1F9 /* Publisher+UIControl.swift */, 3972155A2CA8DB1A009DF1F9 /* Publisher+UIGesture.swift */, 3972155C2CA9007B009DF1F9 /* Publisher+UIBarButtonItem.swift */, + 3F82C3202CADA19300492EEE /* Publisher+UIButton.swift */, ); path = "Combine+"; sourceTree = ""; @@ -1890,6 +1899,7 @@ files = ( 3F3ED28A2BA1A456004E79F0 /* AuthTargetType.swift in Sources */, 396DCDFD2CA19F4500FEF7C8 /* PatchPopupHiddenRequestDTO.swift in Sources */, + 3F82C3222CADA1EF00492EEE /* Publisher+UIButton.swift in Sources */, 3F3ED28B2BA1A456004E79F0 /* PostSocialLoginResponseDTO.swift in Sources */, 3F1F261D2BAA98C8004F75CE /* RemindClipModel.swift in Sources */, 3F3ED28C2BA1A456004E79F0 /* PostRefreshTokenResponseDTO.swift in Sources */, @@ -1900,6 +1910,8 @@ 3F3ED28E2BA1A45C004E79F0 /* PatchPushAlarmRequestDTO.swift in Sources */, 3F3ED2782BA1A246004E79F0 /* KeyChainService.swift in Sources */, 3F1F26202BAAB34B004F75CE /* SelectClipModel.swift in Sources */, + 3F82C3272CADA1FE00492EEE /* Publisher+UIBarButtonItem.swift in Sources */, + 3F82C3242CADA1F700492EEE /* Publisher+Driver.swift in Sources */, 3F1F261F2BAAAED7004F75CE /* ToastStatus.swift in Sources */, 3F3ED2792BA1A258004E79F0 /* Config.swift in Sources */, 3F7D917A2BA1A05F004A022F /* UIView+.swift in Sources */, @@ -1929,6 +1941,7 @@ 3F3ED2A12BA1A478004E79F0 /* GetDetailTimerResponseDTO.swift in Sources */, 3F3ED2942BA1A45F004E79F0 /* GetMainPageResponseDTO.swift in Sources */, 3F3ED2B42BA1D5FF004E79F0 /* NSObject+.swift in Sources */, + 3F82C3232CADA1F400492EEE /* CancelBag.swift in Sources */, 3F3ED2862BA1A400004E79F0 /* PatchEditNameCategoryRequestDTO.swift in Sources */, 3F3ED2972BA1A46E004E79F0 /* GetWeeksLinkResponseDTO.swift in Sources */, 3F3ED29B2BA1A475004E79F0 /* PatchEditTimerRequestDTO.swift in Sources */, @@ -1945,6 +1958,7 @@ 3F3ED2A22BA1A47E004E79F0 /* GetMainPageSearchResponseDTO.swift in Sources */, 3F3ED2A32BA1A47E004E79F0 /* SearchTargetType.swift in Sources */, 396DCDFB2CA19F2000FEF7C8 /* PopupTargetType.swift in Sources */, + 3F82C3252CADA1F900492EEE /* Publisher+UIControl.swift in Sources */, 3F3ED2A42BA1A47E004E79F0 /* GetRecommendSiteResponseDTO.swift in Sources */, 3F3ED2A52BA1A47E004E79F0 /* SearchAPIService.swift in Sources */, 3F3ED2962BA1A46B004E79F0 /* PostSaveLinkRequestDTO.swift in Sources */, @@ -1955,6 +1969,7 @@ 3F3ED2852BA1A3FD004E79F0 /* GetDetailCategoryResponseDTO.swift in Sources */, 3F3ED2802BA1A2B0004E79F0 /* APIInterceptor.swift in Sources */, 3F3ED27F2BA1A2A9004E79F0 /* NetworkService.swift in Sources */, + 3F82C3262CADA1FC00492EEE /* Publisher+UIGesture.swift in Sources */, 3F1F26292BAADE01004F75CE /* Config.swift in Sources */, 3F3ED27C2BA1A29F004E79F0 /* BaseAPIService.swift in Sources */, 3F3ED2B32BA1D59D004E79F0 /* ClipListCollectionViewCell.swift in Sources */, @@ -2096,6 +2111,7 @@ 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */, 6BE6DA732B50C33A008B06FA /* GetAllCategoryResponseDTO.swift in Sources */, 3F2FA1752B45C0AF00EDBF95 /* Config.swift in Sources */, + 3F82C3212CADA19300492EEE /* Publisher+UIButton.swift in Sources */, 6B6AE6AC2B3FF6F7000E2366 /* UIColor+.swift in Sources */, 6BC493682B45D7B100544249 /* ToasterNavigationController.swift in Sources */, 6BE6DA492B50ADC2008B06FA /* NetworkService.swift in Sources */, diff --git a/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIButton.swift b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIButton.swift new file mode 100644 index 00000000..f66bfdeb --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/Combine+/Publisher+UIButton.swift @@ -0,0 +1,17 @@ +// +// Publisher+UIButton.swift +// TOASTER-iOS +// +// Created by ParkJunHyuk on 10/3/24. +// + +import Combine +import UIKit + +extension UIButton { + func tapPublisher() -> AnyPublisher { + publisher(for: .touchUpInside) + .map { _ in () } + .eraseToAnyPublisher() + } +} diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 7aad95a7..5f91c57b 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -195,7 +195,7 @@ private extension ShareViewController { func fetchCheckTokenHealth() { NetworkService.shared.authService.postTokenHealth(tokenType: .accessToken) { [weak self] result in switch result { - case .success(_): + case .success: self?.isUseShareExtension = true case .unAuthorized, .networkFail: self?.isUseShareExtension = false @@ -210,8 +210,8 @@ private extension ShareViewController { func bindViewModel() { let input = ShareViewModel.Input( selectedClip: selectedClipRelay.eraseToAnyPublisher(), - completeButtonTap: completeBottomButton.tapPublisher, - closeButtonTap: closeButton.tapPublisher + completeButtonTap: completeBottomButton.tapPublisher(), + closeButtonTap: closeButton.tapPublisher() ) let output = shareViewModel.transform(input) From efd80e86dcbfed856ed613f6555f3da4f44892ea Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Fri, 4 Oct 2024 16:07:06 +0900 Subject: [PATCH 35/98] =?UTF-8?q?[Fix]=20#207=20-=20section=20index=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 --- TOASTER-iOS/Present/Setting/SettingViewController.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/SettingViewController.swift index bf66f793..1bc5209c 100644 --- a/TOASTER-iOS/Present/Setting/SettingViewController.swift +++ b/TOASTER-iOS/Present/Setting/SettingViewController.swift @@ -199,7 +199,6 @@ private extension SettingViewController { } } - func popupDeleteButtonTapped() { deleteAccount() } @@ -236,7 +235,7 @@ extension SettingViewController: UITableViewDelegate { } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 1 { + if indexPath.section == 0 { switch indexPath.row { case 1: let urlString = "https://open.kakao.com/o/sfN9Fr4f" @@ -255,7 +254,7 @@ extension SettingViewController: UITableViewDelegate { default: return } - } else if indexPath.section == 2 { + } else if indexPath.section == 1 { self.showPopup(forMainText: "정말로 탈퇴하시겠어요?", forSubText: "회원 탈퇴 시 지금까지\n저장한 모든 링크가 사라져요.", forLeftButtonTitle: "네, 탈퇴할래요", forRightButtonTitle: "더 써볼래요", forLeftButtonHandler: self.popupDeleteButtonTapped, forRightButtonHandler: nil) } } From 26bf13bd25f8301ed75991e4bb78dab6191bea8e Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Fri, 4 Oct 2024 16:12:33 +0900 Subject: [PATCH 36/98] =?UTF-8?q?[Chore]=20#207=20-=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EB=B3=80=EA=B2=BD=20(MyPage,=20Setting)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 58 +++++++++---------- .../Model/MypageUserModel.swift | 0 .../Cell}/SettingTableViewCell.swift | 0 .../View/MypageHeaderView.swift | 0 .../Setting/{ => View}/SettingView.swift | 0 .../{ => View}/SettingViewController.swift | 0 6 files changed, 29 insertions(+), 29 deletions(-) rename TOASTER-iOS/Present/{Mypage => Setting}/Model/MypageUserModel.swift (100%) rename TOASTER-iOS/Present/Setting/{ => View/Cell}/SettingTableViewCell.swift (100%) rename TOASTER-iOS/Present/{Mypage => Setting}/View/MypageHeaderView.swift (100%) rename TOASTER-iOS/Present/Setting/{ => View}/SettingView.swift (100%) rename TOASTER-iOS/Present/Setting/{ => View}/SettingViewController.swift (100%) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 85043a71..8e8b2a8d 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -750,9 +750,8 @@ 39B54E712B53C4F100538DAE /* Setting */ = { isa = PBXGroup; children = ( - 39B54E722B53C50300538DAE /* SettingViewController.swift */, - 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */, - 8334CFA12CA979E700319922 /* SettingView.swift */, + 830BD47A2CAFCC8B0050F8D1 /* Model */, + 830BD4792CAFCC1D0050F8D1 /* View */, ); path = Setting; sourceTree = ""; @@ -822,22 +821,6 @@ path = Model; sourceTree = ""; }; - 3F617CB32B4EC2B500956E69 /* View */ = { - isa = PBXGroup; - children = ( - 3F617CB42B4EC2EE00956E69 /* MypageHeaderView.swift */, - ); - path = View; - sourceTree = ""; - }; - 3F617CB62B4ECB4600956E69 /* Model */ = { - isa = PBXGroup; - children = ( - 3F617CB72B4ECB6000956E69 /* MypageUserModel.swift */, - ); - path = Model; - sourceTree = ""; - }; 3FF2BF002BA17492001D7DC1 /* ToasterShareExtension */ = { isa = PBXGroup; children = ( @@ -1026,21 +1009,11 @@ 6B6AE68E2B3FF590000E2366 /* DetailClip */, 6B6AE68B2B3FF585000E2366 /* Remind */, 6BE6D9FF2B4F2A91008B06FA /* RemindAdd */, - 6B6AE6882B3FF575000E2366 /* Mypage */, 6B6AE6522B3FF101000E2366 /* ViewController.swift */, ); path = Present; sourceTree = ""; }; - 6B6AE6882B3FF575000E2366 /* Mypage */ = { - isa = PBXGroup; - children = ( - 3F617CB62B4ECB4600956E69 /* Model */, - 3F617CB32B4EC2B500956E69 /* View */, - ); - path = Mypage; - sourceTree = ""; - }; 6B6AE68B2B3FF585000E2366 /* Remind */ = { isa = PBXGroup; children = ( @@ -1603,6 +1576,33 @@ path = Model; sourceTree = ""; }; + 830BD4792CAFCC1D0050F8D1 /* View */ = { + isa = PBXGroup; + children = ( + 830BD47B2CAFCC910050F8D1 /* Cell */, + 8334CFA12CA979E700319922 /* SettingView.swift */, + 39B54E722B53C50300538DAE /* SettingViewController.swift */, + 3F617CB42B4EC2EE00956E69 /* MypageHeaderView.swift */, + ); + path = View; + sourceTree = ""; + }; + 830BD47A2CAFCC8B0050F8D1 /* Model */ = { + isa = PBXGroup; + children = ( + 3F617CB72B4ECB6000956E69 /* MypageUserModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 830BD47B2CAFCC910050F8D1 /* Cell */ = { + isa = PBXGroup; + children = ( + 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; 8315CD882B5478050061F377 /* SelectClip */ = { isa = PBXGroup; children = ( diff --git a/TOASTER-iOS/Present/Mypage/Model/MypageUserModel.swift b/TOASTER-iOS/Present/Setting/Model/MypageUserModel.swift similarity index 100% rename from TOASTER-iOS/Present/Mypage/Model/MypageUserModel.swift rename to TOASTER-iOS/Present/Setting/Model/MypageUserModel.swift diff --git a/TOASTER-iOS/Present/Setting/SettingTableViewCell.swift b/TOASTER-iOS/Present/Setting/View/Cell/SettingTableViewCell.swift similarity index 100% rename from TOASTER-iOS/Present/Setting/SettingTableViewCell.swift rename to TOASTER-iOS/Present/Setting/View/Cell/SettingTableViewCell.swift diff --git a/TOASTER-iOS/Present/Mypage/View/MypageHeaderView.swift b/TOASTER-iOS/Present/Setting/View/MypageHeaderView.swift similarity index 100% rename from TOASTER-iOS/Present/Mypage/View/MypageHeaderView.swift rename to TOASTER-iOS/Present/Setting/View/MypageHeaderView.swift diff --git a/TOASTER-iOS/Present/Setting/SettingView.swift b/TOASTER-iOS/Present/Setting/View/SettingView.swift similarity index 100% rename from TOASTER-iOS/Present/Setting/SettingView.swift rename to TOASTER-iOS/Present/Setting/View/SettingView.swift diff --git a/TOASTER-iOS/Present/Setting/SettingViewController.swift b/TOASTER-iOS/Present/Setting/View/SettingViewController.swift similarity index 100% rename from TOASTER-iOS/Present/Setting/SettingViewController.swift rename to TOASTER-iOS/Present/Setting/View/SettingViewController.swift From cd99ddc1a466096bc1cada520ae4297db0a7e083 Mon Sep 17 00:00:00 2001 From: Genesis Date: Fri, 4 Oct 2024 18:45:35 +0900 Subject: [PATCH 37/98] =?UTF-8?q?[Fix]=20#205=20-=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 2 ++ .../ShareViewController.swift | 16 +++++++------- ToasterShareExtension/ShareViewModel.swift | 22 +++++-------------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 5f13b41d..31cac401 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -167,6 +167,7 @@ 3FE00F0C2BC632A500CC821E /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA352B5059CE008B06FA /* NetworkResult.swift */; }; 3FE28DC22B879B1400B6AED8 /* OnboardingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE28DC12B879B1400B6AED8 /* OnboardingType.swift */; }; 3FE828352B54E58E00F10732 /* APIInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE828342B54E58E00F10732 /* APIInterceptor.swift */; }; + 3FF02B302CAFE6600074332E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */; }; 3FF2BF092BA17492001D7DC1 /* ToasterShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3FF2BEFF2BA17492001D7DC1 /* ToasterShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3FF2BF0E2BA188AE001D7DC1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B6AE6572B3FF103000E2366 /* Assets.xcassets */; }; 3FF2BF0F2BA188B9001D7DC1 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 398ACFDB2B5E77FA00D5EE77 /* Colors.xcassets */; }; @@ -1952,6 +1953,7 @@ 3F3ED2992BA1A46E004E79F0 /* PatchOpenLinkResponseDTO.swift in Sources */, 3F3ED2872BA1A400004E79F0 /* PostAddCategoryRequestDTO.swift in Sources */, 3F3ED2B52BA1D645004E79F0 /* ClipModel.swift in Sources */, + 3FF02B302CAFE6600074332E /* ViewModelType.swift in Sources */, 83474A6A2BED06EB009B9C48 /* ToasterTargetType.swift in Sources */, 3F3ED2952BA1A46B004E79F0 /* PatchOpenLinkRequestDTO.swift in Sources */, 3F3ED2A92BA1AAB4004E79F0 /* StringLiterals.swift in Sources */, diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 5f91c57b..46101b01 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -26,9 +26,9 @@ class ShareViewController: UIViewController { private var isUseShareExtension = false - private let selectedClipRelay = CurrentValueSubject(nil) + private let selectedClipSubejct = PassthroughSubject() - private var cancellables = Set() + private var cancelBag = CancelBag() // MARK: - UI Components @@ -209,12 +209,12 @@ private extension ShareViewController { func bindViewModel() { let input = ShareViewModel.Input( - selectedClip: selectedClipRelay.eraseToAnyPublisher(), + selectedClip: selectedClipSubejct.eraseToAnyPublisher(), completeButtonTap: completeBottomButton.tapPublisher(), closeButtonTap: closeButton.tapPublisher() ) - let output = shareViewModel.transform(input) + let output = shareViewModel.transform(input, cancelBag: cancelBag) output.isSeleted .sink { [weak self] result in @@ -222,7 +222,7 @@ private extension ShareViewController { self?.completeBottomButton.backgroundColor = .toasterBlack } } - .store(in: &cancellables) + .store(in: cancelBag) output.completeButtonAction .sink { [weak self] result in @@ -230,7 +230,7 @@ private extension ShareViewController { self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) } } - .store(in: &cancellables) + .store(in: cancelBag) output.closeButtonAction .sink { [weak self] _ in @@ -241,7 +241,7 @@ private extension ShareViewController { self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) }) } - .store(in: &cancellables) + .store(in: cancelBag) } } @@ -293,7 +293,7 @@ private extension ShareViewController { extension ShareViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let selectedClip = viewModel.clipData[indexPath.item] - selectedClipRelay.send(selectedClip) + selectedClipSubejct.send(selectedClip) } } diff --git a/ToasterShareExtension/ShareViewModel.swift b/ToasterShareExtension/ShareViewModel.swift index b3ea9352..79a317fa 100644 --- a/ToasterShareExtension/ShareViewModel.swift +++ b/ToasterShareExtension/ShareViewModel.swift @@ -8,20 +8,13 @@ import Foundation import Combine -protocol ViewModelType { - associatedtype Input - associatedtype Output - - func transform(_ input: Input) -> Output -} - final class ShareViewModel: ViewModelType { private let appURL = "TOASTER://" private var urlString = "" struct Input { - let selectedClip: AnyPublisher + let selectedClip: AnyPublisher let completeButtonTap: AnyPublisher let closeButtonTap: AnyPublisher } @@ -32,25 +25,22 @@ final class ShareViewModel: ViewModelType { let closeButtonAction: AnyPublisher } - private var cancellables = Set() - - func transform(_ input: Input) -> Output { + func transform(_ input: Input, cancelBag: CancelBag) -> Output { let categoryIDPublisher = input.selectedClip - .map { clip -> Int? in - guard let clip = clip else { return nil } - return clip.id == 0 ? nil : clip.id + .map { clip in + clip.id == 0 ? nil : clip.id } .eraseToAnyPublisher() let isSelectedPublisher = input.selectedClip - .map { $0 != nil } + .map { _ in true } .eraseToAnyPublisher() let saveLinkResultPublisher = input.completeButtonTap .combineLatest(categoryIDPublisher) .map { _, categoryID in categoryID } .flatMap { [weak self] categoryID -> AnyPublisher in - guard let self = self else { + guard let self else { return Just(false).eraseToAnyPublisher() } From fd017db1a5735b427bff88a4dfaacf5c5ab27db2 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 7 Oct 2024 09:51:25 +0900 Subject: [PATCH 38/98] =?UTF-8?q?[Fix]=20#214=20-=20Share=20Extension=20UR?= =?UTF-8?q?L=20=EB=B0=94=EC=9D=B8=EB=94=A9=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ToasterShareExtension/ShareViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 1f9e2d2c..02b1a940 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -185,7 +185,7 @@ private extension ShareViewController { if itemProvider.hasItemConformingToTypeIdentifier("public.url") { itemProvider.loadItem(forTypeIdentifier: "public.url") { [weak self] (url, error) in if let shareURL = url as? URL { - self?.urlString = shareURL.absoluteString + self?.shareViewModel.bindUrl(shareURL.absoluteString) } else { print("Error loading URL: \(error?.localizedDescription ?? "")") } From c00ceb968b7f638030826b2d8bf5c54bac2f1bf5 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 7 Oct 2024 22:41:12 +0900 Subject: [PATCH 39/98] =?UTF-8?q?[Feat]=20#214=20-=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EB=A1=9C=EB=94=A9=20=EC=9D=B8=EB=94=94=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 12 ++ .../ToasterLoadingView.swift | 125 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 94f41fae..4d494064 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 39A843CA2B74512B007A4D75 /* DetailClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843C92B74512B007A4D75 /* DetailClipViewModel.swift */; }; 39A843CE2B745B3A007A4D75 /* DetailClipPropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */; }; 39A843D12B746420007A4D75 /* EditClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843D02B746420007A4D75 /* EditClipViewModel.swift */; }; + 39AE73C72CB3D41E00F89793 /* ToasterLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */; }; 39B54E732B53C50300538DAE /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B54E722B53C50300538DAE /* SettingViewController.swift */; }; 39B54E7A2B53D49900538DAE /* SettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */; }; 39BC5B0B2B400602004024E6 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B0A2B400602004024E6 /* .swiftlint.yml */; }; @@ -374,6 +375,7 @@ 39A843C92B74512B007A4D75 /* DetailClipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipViewModel.swift; sourceTree = ""; }; 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipPropertyType.swift; sourceTree = ""; }; 39A843D02B746420007A4D75 /* EditClipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClipViewModel.swift; sourceTree = ""; }; + 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterLoadingView.swift; sourceTree = ""; }; 39B54E722B53C50300538DAE /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingTableViewCell.swift; sourceTree = ""; }; 39BC5B0A2B400602004024E6 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; @@ -825,6 +827,14 @@ path = ViewController; sourceTree = ""; }; + 39AE73C52CB3D3DF00F89793 /* ToasterLoadingView */ = { + isa = PBXGroup; + children = ( + 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */, + ); + path = ToasterLoadingView; + sourceTree = ""; + }; 39B54E712B53C4F100538DAE /* Setting */ = { isa = PBXGroup; children = ( @@ -1025,6 +1035,7 @@ 6B6AE6832B3FF514000E2366 /* Components */ = { isa = PBXGroup; children = ( + 39AE73C52CB3D3DF00F89793 /* ToasterLoadingView */, 6BC493662B45D78F00544249 /* ToasterNavigationController */, 398BE7F42B456ACF001595E0 /* ToasterBottomSheet */, 398BE7F12B45628F001595E0 /* ToasterToastMessage */, @@ -2061,6 +2072,7 @@ 39B54E7A2B53D49900538DAE /* SettingTableViewCell.swift in Sources */, 39A843C52B736039007A4D75 /* ClipViewModel.swift in Sources */, 6BE6DA392B50636B008B06FA /* BaseAPIService.swift in Sources */, + 39AE73C72CB3D41E00F89793 /* ToasterLoadingView.swift in Sources */, 397215592CA8D15F009DF1F9 /* Publisher+UIControl.swift in Sources */, 6BE6DA572B50B44F008B06FA /* GetMyPageResponseDTO.swift in Sources */, 6BE6DA182B4FF285008B06FA /* TimerRepeatDate.swift in Sources */, diff --git a/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift b/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift new file mode 100644 index 00000000..53ff1513 --- /dev/null +++ b/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift @@ -0,0 +1,125 @@ +// +// ToasterLoadingView.swift +// TOASTER-iOS +// +// Created by 민 on 10/7/24. +// + +import UIKit + +import SnapKit + +final class ToasterLoadingView: UIView { + + // MARK: - Properties + + /// 현재 로딩 뷰의 애니메이션이 동작하고 있는지를 Bool 값으로 반환 + private(set) var isAnimating: Bool = false + + /// 애니메이션이 중단될 때 로딩 뷰를 사라지게할지/말지를 Bool 값으로 결정 + var hidesWhenStopped: Bool = true + + // MARK: - UI Components + + private let backgroundShapeLayer = CAShapeLayer() + private let loadingShapeLayer = CAShapeLayer() + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + setupStyle() + setupHierarchy() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + setupLayers() + } +} + +// MARK: - Extensions + +extension ToasterLoadingView { + /// 커스텀 로딩 애니메이션을 시작합니다 + func startAnimation() { + guard !isAnimating else { return } + + let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation") + rotationAnimation.toValue = 2 * CGFloat.pi + rotationAnimation.duration = 1 + rotationAnimation.isRemovedOnCompletion = false + rotationAnimation.repeatCount = .infinity + + layer.add(rotationAnimation, forKey: "rotationAnimation") + + isAnimating = true + if hidesWhenStopped { self.isHidden = false } + } + + /// 커스텀 로딩 애니메이션을 멈춥니다 + func stopAnimation() { + guard isAnimating else { return } + layer.removeAnimation(forKey: "rotationAnimation") + + isAnimating = false + if hidesWhenStopped { self.isHidden = true } + } +} + +// MARK: - Private Extensions + +private extension ToasterLoadingView { + func setupStyle() { + backgroundShapeLayer.do { + $0.strokeColor = UIColor.toasterWhite.cgColor + $0.fillColor = UIColor.clear.cgColor + } + + loadingShapeLayer.do { + $0.strokeColor = UIColor.black850.cgColor + $0.fillColor = UIColor.clear.cgColor + $0.strokeEnd = 0.25 + $0.lineCap = .round + } + } + + func setupLayers() { + let centerPoint = CGPoint(x: frame.width / 2, y: bounds.height / 2) + let radius = bounds.width / 2 + + // 흰색 부분의 동그라미 배경 경로 + let backgroundPath = UIBezierPath( + arcCenter: centerPoint, + radius: radius, + startAngle: 0, + endAngle: 2 * CGFloat.pi, + clockwise: true + ) + backgroundShapeLayer.path = backgroundPath.cgPath + backgroundShapeLayer.lineWidth = radius / 3 + + // 검정색 실제 로딩이 되는 부분의 경로 + let loadingPath = UIBezierPath( + arcCenter: centerPoint, + radius: radius, + startAngle: 0, + endAngle: 2 * CGFloat.pi, + clockwise: true + ) + loadingShapeLayer.path = loadingPath.cgPath + loadingShapeLayer.lineWidth = radius / 3 + } + + func setupHierarchy() { + [backgroundShapeLayer, loadingShapeLayer].forEach { + layer.addSublayer($0) + } + } +} + From 5424391e1f7087ebbf3dc7657290a90514afaec0 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 8 Oct 2024 12:28:11 +0900 Subject: [PATCH 40/98] =?UTF-8?q?[Feat]=20#214=20-=20UIButton+=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20&=20=EB=A7=81=ED=81=AC=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=8B=9C=20=EC=A4=91=EB=B3=B5=20=EC=A0=80=EC=9E=A5=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=A7=89=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 ++ .../Global/Extensions/UIKit+/UIButton+.swift | 65 +++++++++++++++++++ .../View/SelectClipViewController.swift | 9 ++- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 4d494064..6aa6379c 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 39A843CE2B745B3A007A4D75 /* DetailClipPropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */; }; 39A843D12B746420007A4D75 /* EditClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843D02B746420007A4D75 /* EditClipViewModel.swift */; }; 39AE73C72CB3D41E00F89793 /* ToasterLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */; }; + 39AE73C92CB41DF200F89793 /* UIButton+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C82CB41DF200F89793 /* UIButton+.swift */; }; 39B54E732B53C50300538DAE /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B54E722B53C50300538DAE /* SettingViewController.swift */; }; 39B54E7A2B53D49900538DAE /* SettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */; }; 39BC5B0B2B400602004024E6 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B0A2B400602004024E6 /* .swiftlint.yml */; }; @@ -376,6 +377,7 @@ 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipPropertyType.swift; sourceTree = ""; }; 39A843D02B746420007A4D75 /* EditClipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClipViewModel.swift; sourceTree = ""; }; 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterLoadingView.swift; sourceTree = ""; }; + 39AE73C82CB41DF200F89793 /* UIButton+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+.swift"; sourceTree = ""; }; 39B54E722B53C50300538DAE /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingTableViewCell.swift; sourceTree = ""; }; 39BC5B0A2B400602004024E6 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; @@ -695,6 +697,7 @@ 6B6AE6A52B3FF6BD000E2366 /* UIView+.swift */, 6B6AE6A32B3FF6B0000E2366 /* UIViewController+.swift */, 830517AF2B4D9A3B009FFB60 /* UILabel+.swift */, + 39AE73C82CB41DF200F89793 /* UIButton+.swift */, ); path = "UIKit+"; sourceTree = ""; @@ -2077,6 +2080,7 @@ 6BE6DA572B50B44F008B06FA /* GetMyPageResponseDTO.swift in Sources */, 6BE6DA182B4FF285008B06FA /* TimerRepeatDate.swift in Sources */, 6BE6D9F22B4EEBC3008B06FA /* RemindViewModel.swift in Sources */, + 39AE73C92CB41DF200F89793 /* UIButton+.swift in Sources */, 6B6AE64F2B3FF101000E2366 /* AppDelegate.swift in Sources */, 6BE6DA922B546F07008B06FA /* TimerTargetType.swift in Sources */, 6BE6DA1A2B4FF443008B06FA /* TimerRepeatBottomSheetView.swift in Sources */, diff --git a/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift new file mode 100644 index 00000000..4343439b --- /dev/null +++ b/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift @@ -0,0 +1,65 @@ +// +// UIButton+.swift +// TOASTER-iOS +// +// Created by 민 on 10/7/24. +// + +import UIKit + +import SnapKit + +extension UIButton { + + /// 버튼 클릭 시 비동기 작업+로딩 애니메이션을 처리하기 위한 메서드입니다. + func loadingButtonTapped( + loadingTitle: String?, + loadingAnimationSize: Int, + task: @escaping (@escaping () -> Void) -> Void + ) { + let originalTitle = self.title(for: .normal) + let originalBackgroundColor = self.backgroundColor + + self.setTitle(loadingTitle, for: .normal) + self.isEnabled = false + self.backgroundColor = .gray200 + + let toasterLoadingView = ToasterLoadingView() + toasterLoadingView.alpha = 0 + self.addSubview(toasterLoadingView) + toasterLoadingView.snp.makeConstraints { + $0.size.equalTo(loadingAnimationSize) + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.titleLabel?.snp.trailing ?? self.snp.centerX) + } + + UIView.animate(withDuration: 0.2, animations: { + toasterLoadingView.snp.updateConstraints { + $0.leading.equalTo(self.titleLabel?.snp.trailing ?? self.snp.centerX).offset(10) + } + self.layoutIfNeeded() + }, completion: { _ in + toasterLoadingView.alpha = 1.0 + toasterLoadingView.startAnimation() + }) + + task { + DispatchQueue.main.async { + UIView.animate(withDuration: 0.2, animations: { + toasterLoadingView.snp.updateConstraints { + $0.leading.equalTo(self.titleLabel?.snp.trailing ?? self.snp.centerX) + } + toasterLoadingView.alpha = 0 + self.layoutIfNeeded() + }, completion: { _ in + toasterLoadingView.stopAnimation() + toasterLoadingView.removeFromSuperview() + self.setTitle(originalTitle, for: .normal) + self.isEnabled = true + self.backgroundColor = originalBackgroundColor + }) + } + } + } +} + diff --git a/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift b/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift index a1d1f4bd..ae990d40 100644 --- a/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift +++ b/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift @@ -186,8 +186,13 @@ private extension SelectClipViewController { } @objc func completeButtonTapped() { - viewModel.postSaveLink(url: linkURL, - category: categoryID) + completeButton.loadingButtonTapped( + loadingTitle: "저장 중...", + loadingAnimationSize: 16, + task: { _ in + self.viewModel.postSaveLink(url: self.linkURL, category: self.categoryID) + } + ) } } From d5513c267feb37a07e4992121d4365d1fd38ae86 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 8 Oct 2024 13:29:28 +0900 Subject: [PATCH 41/98] =?UTF-8?q?[Feat]=20#214=20-=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=8B=9C=201=EC=B4=88=20=EC=A7=80?= =?UTF-8?q?=EC=97=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 ++++ TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift | 3 +-- .../AddLink/SelectClip/View/SelectClipViewController.swift | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 6aa6379c..65150f2e 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -52,6 +52,8 @@ 39A843D12B746420007A4D75 /* EditClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843D02B746420007A4D75 /* EditClipViewModel.swift */; }; 39AE73C72CB3D41E00F89793 /* ToasterLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */; }; 39AE73C92CB41DF200F89793 /* UIButton+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C82CB41DF200F89793 /* UIButton+.swift */; }; + 39AE73CC2CB4EADB00F89793 /* UIButton+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C82CB41DF200F89793 /* UIButton+.swift */; }; + 39AE73CD2CB4EBC300F89793 /* ToasterLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AE73C62CB3D41E00F89793 /* ToasterLoadingView.swift */; }; 39B54E732B53C50300538DAE /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B54E722B53C50300538DAE /* SettingViewController.swift */; }; 39B54E7A2B53D49900538DAE /* SettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B54E792B53D49900538DAE /* SettingTableViewCell.swift */; }; 39BC5B0B2B400602004024E6 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 39BC5B0A2B400602004024E6 /* .swiftlint.yml */; }; @@ -1974,6 +1976,7 @@ 3F82C3252CADA1F900492EEE /* Publisher+UIControl.swift in Sources */, 3F3ED2A42BA1A47E004E79F0 /* GetRecommendSiteResponseDTO.swift in Sources */, 3F3ED2A52BA1A47E004E79F0 /* SearchAPIService.swift in Sources */, + 39AE73CD2CB4EBC300F89793 /* ToasterLoadingView.swift in Sources */, 3F3ED2962BA1A46B004E79F0 /* PostSaveLinkRequestDTO.swift in Sources */, 3F3ED2882BA1A400004E79F0 /* PatchEditPriorityCategoryRequestDTO.swift in Sources */, 3FE00F082BC5076200CC821E /* PostTokenHealthResponseDTO.swift in Sources */, @@ -1988,6 +1991,7 @@ 3F3ED2B32BA1D59D004E79F0 /* ClipListCollectionViewCell.swift in Sources */, 3F3ED2B72BA1D897004E79F0 /* SearchResultModel.swift in Sources */, 3F3ED2812BA1A3F5004E79F0 /* ClipAPIService.swift in Sources */, + 39AE73CC2CB4EADB00F89793 /* UIButton+.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift index 4343439b..51444495 100644 --- a/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift +++ b/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift @@ -15,7 +15,7 @@ extension UIButton { func loadingButtonTapped( loadingTitle: String?, loadingAnimationSize: Int, - task: @escaping (@escaping () -> Void) -> Void + task: @escaping (_ completion: @escaping () -> Void) -> Void ) { let originalTitle = self.title(for: .normal) let originalBackgroundColor = self.backgroundColor @@ -62,4 +62,3 @@ extension UIButton { } } } - diff --git a/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift b/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift index ae990d40..752d81f9 100644 --- a/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift +++ b/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift @@ -190,7 +190,9 @@ private extension SelectClipViewController { loadingTitle: "저장 중...", loadingAnimationSize: 16, task: { _ in - self.viewModel.postSaveLink(url: self.linkURL, category: self.categoryID) + DispatchQueue.global().asyncAfter(deadline: .now() + 1) { + self.viewModel.postSaveLink(url: self.linkURL, category: self.categoryID) + } } ) } From f462442e508da268e54568c9d0a88a937443412a Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 8 Oct 2024 15:47:56 +0900 Subject: [PATCH 42/98] =?UTF-8?q?[Chore]=20#214=20-=20=ED=83=88=EC=B6=9C?= =?UTF-8?q?=20=ED=81=B4=EB=A1=9C=EC=A0=80=20=EC=82=AC=EC=9A=A9=20=EB=B0=A9?= =?UTF-8?q?=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 --- TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift b/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift index 51444495..30226cf6 100644 --- a/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift +++ b/TOASTER-iOS/Global/Extensions/UIKit+/UIButton+.swift @@ -15,7 +15,7 @@ extension UIButton { func loadingButtonTapped( loadingTitle: String?, loadingAnimationSize: Int, - task: @escaping (_ completion: @escaping () -> Void) -> Void + task: (@escaping () -> Void) -> Void ) { let originalTitle = self.title(for: .normal) let originalBackgroundColor = self.backgroundColor From 47ac6687a83641e071b29a4ea91e77dc61667a96 Mon Sep 17 00:00:00 2001 From: Genesis Date: Tue, 8 Oct 2024 17:10:23 +0900 Subject: [PATCH 43/98] =?UTF-8?q?[Feat]=20#217=20-=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20API=20=EA=B4=80=EB=A0=A8=20DTO=20=EB=B0=8F?= =?UTF-8?q?=20Service=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 10 ++++++++++ .../PatchChangeCategoryRequestDTO.swift | 13 +++++++++++++ .../PatchChangeCategoryResponseDTO.swift | 12 ++++++++++++ .../Network/Toaster/ToasterAPIService.swift | 19 +++++++++++++++++++ .../Network/Toaster/ToasterTargetType.swift | 4 ++++ 5 files changed, 58 insertions(+) create mode 100644 TOASTER-iOS/Network/Toaster/DTO/Request/PatchChangeCategoryRequestDTO.swift create mode 100644 TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 94f41fae..867aefe5 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -166,6 +166,9 @@ 3FE00F0C2BC632A500CC821E /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA352B5059CE008B06FA /* NetworkResult.swift */; }; 3FE28DC22B879B1400B6AED8 /* OnboardingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE28DC12B879B1400B6AED8 /* OnboardingType.swift */; }; 3FE828352B54E58E00F10732 /* APIInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE828342B54E58E00F10732 /* APIInterceptor.swift */; }; + 3FEA674B2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */; }; + 3FEA674D2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674C2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift */; }; + 3FEA674E2CB51E6D00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */; }; 3FF02B302CAFE6600074332E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */; }; 3FF2BF092BA17492001D7DC1 /* ToasterShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3FF2BEFF2BA17492001D7DC1 /* ToasterShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3FF2BF0E2BA188AE001D7DC1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B6AE6572B3FF103000E2366 /* Assets.xcassets */; }; @@ -417,6 +420,8 @@ 3FE00F0A2BC6328900CC821E /* MoyaPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoyaPlugin.swift; sourceTree = ""; }; 3FE28DC12B879B1400B6AED8 /* OnboardingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingType.swift; sourceTree = ""; }; 3FE828342B54E58E00F10732 /* APIInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIInterceptor.swift; sourceTree = ""; }; + 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchChangeCategoryRequestDTO.swift; sourceTree = ""; }; + 3FEA674C2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchChangeCategoryResponseDTO.swift; sourceTree = ""; }; 3FF2BEFF2BA17492001D7DC1 /* ToasterShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ToasterShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 3FF2BF062BA17492001D7DC1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6B0E85D82B564913001BC15F /* RemindTimerAddViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindTimerAddViewModel.swift; sourceTree = ""; }; @@ -1502,6 +1507,7 @@ 6BE6DA812B545786008B06FA /* PostSaveLinkRequestDTO.swift */, 390247A92B58016C00F9A86A /* PatchOpenLinkRequestDTO.swift */, 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */, + 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */, ); path = Request; sourceTree = ""; @@ -1512,6 +1518,7 @@ 6BE6DA872B546B24008B06FA /* PatchOpenLinkResponseDTO.swift */, 6BE6DA8B2B546CA9008B06FA /* GetWeeksLinkResponseDTO.swift */, 8364220B2BE7BFB2005C4085 /* PatchEditLinkTitleResponseDTO.swift */, + 3FEA674C2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift */, ); path = Response; sourceTree = ""; @@ -1945,6 +1952,7 @@ 3F3ED29B2BA1A475004E79F0 /* PatchEditTimerRequestDTO.swift in Sources */, 3F3ED29C2BA1A475004E79F0 /* PatchEditTimerTitleRequestDTO.swift in Sources */, 3F3ED29D2BA1A475004E79F0 /* PostCreateTimerRequestDTO.swift in Sources */, + 3FEA674E2CB51E6D00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */, 3F1F26262BAAC231004F75CE /* ShareViewController.swift in Sources */, 3F1F26222BAAB395004F75CE /* ToasterToastMessageView.swift in Sources */, 3F3ED2992BA1A46E004E79F0 /* PatchOpenLinkResponseDTO.swift in Sources */, @@ -2048,6 +2056,8 @@ 830517962B4D21BB009FFB60 /* CompositioinalFactory.swift in Sources */, 6BE6D9E82B4EA773008B06FA /* RemindCollectionFooterView.swift in Sources */, 39A843CA2B74512B007A4D75 /* DetailClipViewModel.swift in Sources */, + 3FEA674D2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift in Sources */, + 3FEA674B2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */, 83CFC3392B568BE700A2EB2B /* RecommendSiteModel.swift in Sources */, 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */, 6BE6D9F42B4EF568008B06FA /* RemindTimerEditBottomSheetView.swift in Sources */, diff --git a/TOASTER-iOS/Network/Toaster/DTO/Request/PatchChangeCategoryRequestDTO.swift b/TOASTER-iOS/Network/Toaster/DTO/Request/PatchChangeCategoryRequestDTO.swift new file mode 100644 index 00000000..45890569 --- /dev/null +++ b/TOASTER-iOS/Network/Toaster/DTO/Request/PatchChangeCategoryRequestDTO.swift @@ -0,0 +1,13 @@ +// +// PatchChangeCategoryRequestDTO.swift +// TOASTER-iOS +// +// Created by ParkJunHyuk on 10/8/24. +// + +import Foundation + +struct PatchChangeCategoryRequestDTO: Codable { + let toastId: Int + let categoryId: Int +} diff --git a/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift b/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift new file mode 100644 index 00000000..7776b28d --- /dev/null +++ b/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift @@ -0,0 +1,12 @@ +// +// PatchChangeCategoryResponseDTO.swift +// TOASTER-iOS +// +// Created by ParkJunHyuk on 10/8/24. +// + +import Foundation + +struct PatchChangeCategoryResponseDTO: Decodable { + let categoryId: Int +} diff --git a/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift b/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift index 684165f7..f8424c1e 100644 --- a/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift +++ b/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift @@ -19,6 +19,8 @@ protocol ToasterAPIServiceProtocol { func getWeeksLink(completion: @escaping (NetworkResult) -> Void) func patchEditLinkTitle(requestBody: PatchEditLinkTitleRequestDTO, completion: @escaping (NetworkResult) -> Void) + func patchChangeCategory(requestBody: PatchChangeCategoryRequestDTO, + completion: @escaping (NetworkResult) -> Void) } final class ToasterAPIService: BaseAPIService, ToasterAPIServiceProtocol { @@ -108,4 +110,21 @@ final class ToasterAPIService: BaseAPIService, ToasterAPIServiceProtocol { } } } + + func patchChangeCategory(requestBody: PatchChangeCategoryRequestDTO, + completion: @escaping (NetworkResult) -> Void) { + provider.request(.patchChangeCategory(requestBody: requestBody)) { result in + switch result { + case .success(let response): + let networkResult: NetworkResult = self.fetchNetworkResult(statusCode: response.statusCode, data: response.data) + print(networkResult.stateDescription) + completion(networkResult) + case .failure(let error): + if let response = error.response { + let networkResult: NetworkResult = self.fetchNetworkResult(statusCode: response.statusCode, data: response.data) + completion(networkResult) + } + } + } + } } diff --git a/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift b/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift index 8c59b0a1..ba4334f7 100644 --- a/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift +++ b/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift @@ -15,6 +15,7 @@ enum ToasterTargetType { case deleteLink(toastId: Int) case getWeeksLink case patchEditLinkTitle(requestBody: PatchEditLinkTitleRequestDTO) + case patchChangeCategory(requestBody: PatchChangeCategoryRequestDTO) } extension ToasterTargetType: BaseTargetType { @@ -35,6 +36,7 @@ extension ToasterTargetType: BaseTargetType { case .postSaveLink(let body): return body case .patchOpenLink(let body): return body case .patchEditLinkTitle(let body): return body + case .patchChangeCategory(let body): return body default: return .none } } @@ -46,6 +48,7 @@ extension ToasterTargetType: BaseTargetType { case .deleteLink: return utilPath.rawValue + "/delete" case .getWeeksLink: return utilPath.rawValue + "/week" case .patchEditLinkTitle: return utilPath.rawValue + "/title" + case .patchChangeCategory: return utilPath.rawValue + "/category" } } @@ -56,6 +59,7 @@ extension ToasterTargetType: BaseTargetType { case .deleteLink: return .delete case .getWeeksLink: return .get case .patchEditLinkTitle: return .patch + case .patchChangeCategory: return .patch } } } From 90f2655aca37422296101d097c265c6b02f07c7c Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Wed, 9 Oct 2024 16:19:55 +0900 Subject: [PATCH 44/98] =?UTF-8?q?[Feat]=20#214=20-=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=A7=80=EC=97=B0=EC=8B=9C=EA=B0=84=20=EB=8B=A8=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddLink/SelectClip/View/SelectClipViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift b/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift index 752d81f9..8d204f09 100644 --- a/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift +++ b/TOASTER-iOS/Present/AddLink/SelectClip/View/SelectClipViewController.swift @@ -190,7 +190,7 @@ private extension SelectClipViewController { loadingTitle: "저장 중...", loadingAnimationSize: 16, task: { _ in - DispatchQueue.global().asyncAfter(deadline: .now() + 1) { + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { self.viewModel.postSaveLink(url: self.linkURL, category: self.categoryID) } } From 850d869d4e7caef7d1d0ceacc35e073c1659f099 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 14 Oct 2024 13:13:12 +0900 Subject: [PATCH 45/98] =?UTF-8?q?[Design]=20#215=20-=20search=20asset=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Icons_24/ic_search_24.imageset/Contents.json | 12 ++++++++++++ .../Icons_24/ic_search_24.imageset/ic_search_24.svg | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/Contents.json create mode 100644 TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/ic_search_24.svg diff --git a/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/Contents.json b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/Contents.json new file mode 100644 index 00000000..45c37726 --- /dev/null +++ b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_search_24.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/ic_search_24.svg b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/ic_search_24.svg new file mode 100644 index 00000000..5119069c --- /dev/null +++ b/TOASTER-iOS/Global/Resources/Assets/Assets.xcassets/Icons_24/ic_search_24.imageset/ic_search_24.svg @@ -0,0 +1,3 @@ + + + From 679ae0ea4f1f8de425d872a2ad455e1ed7b0427e Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 14 Oct 2024 13:43:05 +0900 Subject: [PATCH 46/98] =?UTF-8?q?[Feat]=20#215=20-=20TabBarItem=20Search?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Global/Consts/StringLiterals.swift | 2 +- TOASTER-iOS/Present/TabBar/TabBarItem.swift | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/TOASTER-iOS/Global/Consts/StringLiterals.swift b/TOASTER-iOS/Global/Consts/StringLiterals.swift index 6a045f84..9da41670 100644 --- a/TOASTER-iOS/Global/Consts/StringLiterals.swift +++ b/TOASTER-iOS/Global/Consts/StringLiterals.swift @@ -21,7 +21,7 @@ enum StringLiterals { static let home = "HOME" static let clip = "CLIP" static let timer = "TIMER" - static let my = "MY" + static let search = "SEARCH" } enum Button { diff --git a/TOASTER-iOS/Present/TabBar/TabBarItem.swift b/TOASTER-iOS/Present/TabBar/TabBarItem.swift index 381162cd..2d432326 100644 --- a/TOASTER-iOS/Present/TabBar/TabBarItem.swift +++ b/TOASTER-iOS/Present/TabBar/TabBarItem.swift @@ -9,7 +9,7 @@ import UIKit enum TabBarItem: CaseIterable { - case home, clip, plus, timer, my + case home, clip, plus, search, timer // 선택되지 않은 탭 var normalItem: UIImage? { @@ -20,10 +20,11 @@ enum TabBarItem: CaseIterable { return .icClipFull24.withTintColor(.gray150) case .plus: return .fabPlus + case .search: + return .icSearch24.withTintColor(.gray150) case .timer: return .icTimer24.withTintColor(.gray150) - case .my: - return .icMy24.withTintColor(.gray150) + } } @@ -36,10 +37,10 @@ enum TabBarItem: CaseIterable { return .icClipFull24.withTintColor(.black900) case .plus: return .fabPlus + case .search: + return .icSearch24.withTintColor(.black900) case .timer: return .icTimer24.withTintColor(.black900) - case .my: - return .icMy24.withTintColor(.black900) } } @@ -49,8 +50,8 @@ enum TabBarItem: CaseIterable { case .home: return StringLiterals.Tabbar.home case .clip: return StringLiterals.Tabbar.clip case .plus: return nil + case .search: return StringLiterals.Tabbar.search case .timer: return StringLiterals.Tabbar.timer - case .my: return StringLiterals.Tabbar.my } } @@ -60,8 +61,8 @@ enum TabBarItem: CaseIterable { case .home: return HomeViewController() case .clip: return ClipViewController() case .plus: return ViewController() + case .search: return SearchViewController() case .timer: return RemindViewController() - case .my: return ViewController() } } } From 7e0f9cc2ad7d81844fbb1134873597577f1fa294 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 14 Oct 2024 13:55:17 +0900 Subject: [PATCH 47/98] [Feat] #215 - SearchViewController navigationBar --- .../Search/View/SearchViewController.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/TOASTER-iOS/Present/Search/View/SearchViewController.swift b/TOASTER-iOS/Present/Search/View/SearchViewController.swift index a43c5169..3550a1d9 100644 --- a/TOASTER-iOS/Present/Search/View/SearchViewController.swift +++ b/TOASTER-iOS/Present/Search/View/SearchViewController.swift @@ -51,13 +51,7 @@ final class SearchViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - hideNavigationBar() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - showNavigationBar() + setupNavigationBar() } } @@ -195,6 +189,18 @@ private extension SearchViewController { self.changeViewController(viewController: LoginViewController()) } + func setupNavigationBar() { + let type: ToasterNavigationType = ToasterNavigationType(hasBackButton: false, + hasRightButton: false, + mainTitle: StringOrImageType.string(StringLiterals.Tabbar.search), + rightButton: StringOrImageType.string(""), + rightButtonAction: {}) + + if let navigationController = navigationController as? ToasterNavigationController { + navigationController.setupNavigationBar(forType: type) + } + } + @objc func searchButtonTapped() { fetchSearchResult() } From 8aff0904365e543886a5f6fe1ac3558bda90c34a Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 14 Oct 2024 14:05:44 +0900 Subject: [PATCH 48/98] =?UTF-8?q?[Feat]=20#220=20-=20=ED=88=B4=ED=8C=81=20?= =?UTF-8?q?=EC=9E=AC=EC=82=AC=EC=9A=A9=20UI=20Components=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 20 +++ .../ToasterTipView/TipPathView.swift | 94 ++++++++++++++ .../ToasterTipView/ToasterTipView.swift | 118 ++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 TOASTER-iOS/Global/Components/ToasterTipView/TipPathView.swift create mode 100644 TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 65150f2e..e854ca77 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -46,6 +46,10 @@ 398BE7FA2B467E4B001595E0 /* ClipListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7F92B467E4B001595E0 /* ClipListCollectionViewCell.swift */; }; 398BE7FC2B468F80001595E0 /* ClipCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7FB2B468F80001595E0 /* ClipCollectionHeaderView.swift */; }; 398BE7FE2B46C164001595E0 /* AddClipBottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE7FD2B46C164001595E0 /* AddClipBottomSheetView.swift */; }; + 39A232DD2CB8F28000ACC803 /* TipPathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A232DC2CB8F28000ACC803 /* TipPathView.swift */; }; + 39A232DE2CB8F28000ACC803 /* TipPathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A232DC2CB8F28000ACC803 /* TipPathView.swift */; }; + 39A232E02CB8F29A00ACC803 /* ToasterTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A232DF2CB8F29A00ACC803 /* ToasterTipView.swift */; }; + 39A232E12CB8F29A00ACC803 /* ToasterTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A232DF2CB8F29A00ACC803 /* ToasterTipView.swift */; }; 39A843C52B736039007A4D75 /* ClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843C42B736039007A4D75 /* ClipViewModel.swift */; }; 39A843CA2B74512B007A4D75 /* DetailClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843C92B74512B007A4D75 /* DetailClipViewModel.swift */; }; 39A843CE2B745B3A007A4D75 /* DetailClipPropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */; }; @@ -374,6 +378,8 @@ 398BE7F92B467E4B001595E0 /* ClipListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipListCollectionViewCell.swift; sourceTree = ""; }; 398BE7FB2B468F80001595E0 /* ClipCollectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipCollectionHeaderView.swift; sourceTree = ""; }; 398BE7FD2B46C164001595E0 /* AddClipBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddClipBottomSheetView.swift; sourceTree = ""; }; + 39A232DC2CB8F28000ACC803 /* TipPathView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipPathView.swift; sourceTree = ""; }; + 39A232DF2CB8F29A00ACC803 /* ToasterTipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToasterTipView.swift; sourceTree = ""; }; 39A843C42B736039007A4D75 /* ClipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipViewModel.swift; sourceTree = ""; }; 39A843C92B74512B007A4D75 /* DetailClipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipViewModel.swift; sourceTree = ""; }; 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipPropertyType.swift; sourceTree = ""; }; @@ -762,6 +768,15 @@ path = View; sourceTree = ""; }; + 39A232DB2CB8F1B900ACC803 /* ToasterTipView */ = { + isa = PBXGroup; + children = ( + 39A232DF2CB8F29A00ACC803 /* ToasterTipView.swift */, + 39A232DC2CB8F28000ACC803 /* TipPathView.swift */, + ); + path = ToasterTipView; + sourceTree = ""; + }; 39A843C32B73602C007A4D75 /* ViewModel */ = { isa = PBXGroup; children = ( @@ -1040,6 +1055,7 @@ 6B6AE6832B3FF514000E2366 /* Components */ = { isa = PBXGroup; children = ( + 39A232DB2CB8F1B900ACC803 /* ToasterTipView */, 39AE73C52CB3D3DF00F89793 /* ToasterLoadingView */, 6BC493662B45D78F00544249 /* ToasterNavigationController */, 398BE7F42B456ACF001595E0 /* ToasterBottomSheet */, @@ -1934,6 +1950,7 @@ 3FE00F0C2BC632A500CC821E /* NetworkResult.swift in Sources */, 83474A6D2BED0750009B9C48 /* PatchEditLinkTitleResponseDTO.swift in Sources */, 3F3ED2832BA1A3FD004E79F0 /* GetAllCategoryResponseDTO.swift in Sources */, + 39A232DD2CB8F28000ACC803 /* TipPathView.swift in Sources */, 3F1F261C2BAA9887004F75CE /* RemindSelectClipViewModel.swift in Sources */, 3F3ED28F2BA1A45F004E79F0 /* UserAPIService.swift in Sources */, 3F3ED2902BA1A45F004E79F0 /* PatchPushAlarmResponseDTO.swift in Sources */, @@ -1948,6 +1965,7 @@ 3F3ED29E2BA1A478004E79F0 /* TimerAPIService.swift in Sources */, 396DCE002CA19F5C00FEF7C8 /* GetPopupInfoResponseDTO.swift in Sources */, 3F3ED29F2BA1A478004E79F0 /* GetTimerMainpageResponseDTO.swift in Sources */, + 39A232E02CB8F29A00ACC803 /* ToasterTipView.swift in Sources */, 3F3ED2A02BA1A478004E79F0 /* TimerTargetType.swift in Sources */, 3F3ED2AA2BA1AAB7004E79F0 /* FontLiterals.swift in Sources */, 3F3ED2A62BA1A48F004E79F0 /* NoneDataResponseDTO.swift in Sources */, @@ -2064,6 +2082,7 @@ 6B0E85DC2B564949001BC15F /* RemindTimerAddModel.swift in Sources */, 6BE6DA512B50B309008B06FA /* PatchPushAlarmRequestDTO.swift in Sources */, 830517962B4D21BB009FFB60 /* CompositioinalFactory.swift in Sources */, + 39A232E12CB8F29A00ACC803 /* ToasterTipView.swift in Sources */, 6BE6D9E82B4EA773008B06FA /* RemindCollectionFooterView.swift in Sources */, 39A843CA2B74512B007A4D75 /* DetailClipViewModel.swift in Sources */, 83CFC3392B568BE700A2EB2B /* RecommendSiteModel.swift in Sources */, @@ -2173,6 +2192,7 @@ 39EF96012B501E8A00F301FC /* EditClipNoticeView.swift in Sources */, 8315CD8E2B547EE30061F377 /* SelectClipHeaderView.swift in Sources */, 6B6AE6A22B3FF5F7000E2366 /* TabBarController.swift in Sources */, + 39A232DE2CB8F28000ACC803 /* TipPathView.swift in Sources */, 3F2BFAC92B40370D00DA76B7 /* SocialLoginButtonView.swift in Sources */, 6BE6DA432B50A999008B06FA /* PostRefreshTokenResponseDTO.swift in Sources */, 830471622B889641005AEEB4 /* HomeViewModel.swift in Sources */, diff --git a/TOASTER-iOS/Global/Components/ToasterTipView/TipPathView.swift b/TOASTER-iOS/Global/Components/ToasterTipView/TipPathView.swift new file mode 100644 index 00000000..437fdeb6 --- /dev/null +++ b/TOASTER-iOS/Global/Components/ToasterTipView/TipPathView.swift @@ -0,0 +1,94 @@ +// +// TipType.swift +// TOASTER-iOS +// +// Created by 민 on 10/11/24. +// + +import UIKit + +import SnapKit + +enum TipType { + case top, bottom, left, right +} + +final class TipPathView: UIView { + + // MARK: - Properties + + private var tipType: TipType + + private let arrowWidth: CGFloat = 10.0 + private let arrowHeight: CGFloat = 9.0 + + // MARK: - UI Components + + private let tipPath = UIBezierPath() + + // MARK: - Life Cycles + + init(tipType: TipType) { + self.tipType = tipType + super.init(frame: .zero) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ rect: CGRect) { + switch tipType { + case .top: drawTopTip(rect) + case .bottom: drawBottomTip(rect) + case .left: drawLeftTip(rect) + case .right: drawRightTip(rect) + } + setupTip() + } +} + +// MARK: - Private Extensions + +private extension TipPathView { + func setupTip() { + tipPath.do { + $0.lineJoinStyle = .round + $0.lineWidth = 2 + tipPath.close() + UIColor.black900.setStroke() + tipPath.stroke() + UIColor.black900.setFill() + tipPath.fill() + } + } + + /// 팁이 상단에 위치했을 때 - 아래를 가리키는 방향 + func drawTopTip(_ rect: CGRect) { + tipPath.move(to: CGPoint(x: rect.midX - arrowWidth/2, y: rect.maxY - arrowHeight)) + tipPath.addLine(to: CGPoint(x: rect.midX, y: rect.maxY)) + tipPath.addLine(to: CGPoint(x: rect.midX + arrowWidth/2, y: rect.maxY - arrowHeight)) + } + + /// 팁이 하단에 위치했을 때 - 위를 가리키는 방향 + func drawBottomTip(_ rect: CGRect) { + tipPath.move(to: CGPoint(x: rect.midX - arrowWidth/2, y: rect.minY + arrowHeight)) + tipPath.addLine(to: CGPoint(x: rect.midX, y: rect.minY)) + tipPath.addLine(to: CGPoint(x: rect.midX + arrowWidth/2, y: rect.minY + arrowHeight)) + } + + /// 팁이 좌측에 위치했을 때 - 오른쪽을 가리키는 방향 + func drawLeftTip(_ rect: CGRect) { + tipPath.move(to: CGPoint(x: rect.maxX - arrowHeight, y: rect.midY - arrowWidth/2)) + tipPath.addLine(to: CGPoint(x: rect.maxX, y: rect.midY)) + tipPath.addLine(to: CGPoint(x: rect.maxX - arrowHeight, y: rect.midY + arrowWidth/2)) + } + + /// 팁이 우측에 위치했을 때 - 왼쪽을 가리키는 방향 + func drawRightTip(_ rect: CGRect) { + tipPath.move(to: CGPoint(x: rect.minX + arrowHeight, y: rect.midY - arrowWidth/2)) + tipPath.addLine(to: CGPoint(x: rect.minX, y: rect.midY)) + tipPath.addLine(to: CGPoint(x: rect.minX + arrowHeight, y: rect.midY + arrowWidth/2)) + } +} diff --git a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift new file mode 100644 index 00000000..8452cea5 --- /dev/null +++ b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift @@ -0,0 +1,118 @@ +// +// ToasterTipView.swift +// TOASTER-iOS +// +// Created by 민 on 10/11/24. +// + +import UIKit + +import SnapKit + +final class ToasterTipView: UIView { + + // MARK: - Properties + + private let title: String + private let tipType: TipType + + // MARK: - UI Components + + private let sourceView: UIView + + private let containerView = UIView() + private let tipLabel = UILabel() + private lazy var tipPathView = TipPathView(tipType: tipType) + + // MARK: - Life Cycles + + init(title: String, type: TipType, sourceItem: UIView) { + self.title = title + self.tipType = type + self.sourceView = sourceItem + super.init(frame: .zero) + setupStyle() + setupHierarchy() + setupLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension ToasterTipView { + /// 툴팁을 보여줄 때 호출하는 함수 + func showTooltip() { + switch tipType { + case .top: + self.snp.makeConstraints { + $0.bottom.equalTo(sourceView.snp.top).offset(-8) + $0.centerX.equalTo(sourceView.snp.centerX) + } + case .bottom: + self.snp.makeConstraints { + $0.top.equalTo(sourceView.snp.bottom).offset(8) + $0.centerX.equalTo(sourceView.snp.centerX) + } + case .left: + self.snp.makeConstraints { + $0.right.equalTo(sourceView.snp.left).offset(-8) + $0.centerY.equalTo(sourceView.snp.centerY) + } + case .right: + self.snp.makeConstraints { + $0.left.equalTo(sourceView.snp.right).offset(8) + $0.centerY.equalTo(sourceView.snp.centerY) + } + } + } +} + + +// MARK: - Private Extensions + +private extension ToasterTipView { + func setupStyle() { + backgroundColor = .clear + + tipPathView.do { + $0.backgroundColor = .clear + } + + containerView.do { + $0.backgroundColor = .black900 + $0.makeRounded(radius: 8) + } + + tipLabel.do { + $0.text = title + $0.font = .suitMedium(size: 12) + $0.textColor = .toasterWhite + $0.textAlignment = .center + } + } + + func setupHierarchy() { + addSubviews(tipPathView, containerView) + containerView.addSubviews(tipLabel) + } + + func setupLayout() { + tipPathView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + containerView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(9) + } + + tipLabel.snp.makeConstraints { + $0.top.bottom.equalToSuperview().inset(8) + $0.leading.trailing.equalToSuperview().inset(10) + } + } +} From 5d83c1cc8415e09e553a6d946e07f6c2e84ca0f8 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 14 Oct 2024 14:06:19 +0900 Subject: [PATCH 49/98] =?UTF-8?q?[Chore]=20#215=20-=20SearchView=20UI=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 --- .../Search/View/SearchViewController.swift | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/TOASTER-iOS/Present/Search/View/SearchViewController.swift b/TOASTER-iOS/Present/Search/View/SearchViewController.swift index 3550a1d9..81400d8b 100644 --- a/TOASTER-iOS/Present/Search/View/SearchViewController.swift +++ b/TOASTER-iOS/Present/Search/View/SearchViewController.swift @@ -29,7 +29,6 @@ final class SearchViewController: UIViewController { private let navigationBar: UIView = UIView() private let searchTextField: UITextField = UITextField() - private let backButton: UIButton = UIButton() private let searchButton: UIButton = UIButton() private let clearButton: UIButton = UIButton() @@ -68,11 +67,6 @@ private extension SearchViewController { $0.backgroundColor = .toasterBackground } - backButton.do { - $0.setImage(.icArrowLeft24, for: .normal) - $0.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) - } - searchButton.do { $0.setImage(.icSearch20, for: .normal) $0.addTarget(self, action: #selector(searchButtonTapped), for: .touchUpInside) @@ -106,7 +100,7 @@ private extension SearchViewController { func setupHierarchy() { view.addSubviews(navigationBar, emptyView, searchResultCollectionView) - navigationBar.addSubviews(backButton, searchTextField) + navigationBar.addSubview(searchTextField) searchTextField.addSubviews(searchButton, clearButton) } @@ -117,12 +111,6 @@ private extension SearchViewController { $0.horizontalEdges.equalToSuperview() } - backButton.snp.makeConstraints { - $0.width.height.equalTo(24) - $0.centerY.equalToSuperview() - $0.leading.equalToSuperview().inset(20) - } - [searchButton, clearButton].forEach { $0.snp.makeConstraints { $0.width.height.equalTo(20) @@ -133,9 +121,8 @@ private extension SearchViewController { searchTextField.snp.makeConstraints { $0.height.equalTo(42) - $0.centerY.equalToSuperview() - $0.leading.equalTo(backButton.snp.trailing).offset(12) - $0.trailing.equalToSuperview().inset(20) + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview().inset(20) } emptyView.snp.makeConstraints { @@ -210,10 +197,6 @@ private extension SearchViewController { searchTextField.text = nil searchTextField.becomeFirstResponder() } - - @objc func backButtonTapped() { - navigationController?.popViewController(animated: true) - } } // MARK: - UITextFieldDelegate From c3a2760b284173ac27cc662c4b3ad102befd9d00 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Mon, 14 Oct 2024 14:13:49 +0900 Subject: [PATCH 50/98] =?UTF-8?q?[Chore]=20#215=20-=20HomeView=20searchBut?= =?UTF-8?q?ton=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Cell/MainCollectionViewCell.swift | 37 +------------------ .../Component/CompositioinalFactory.swift | 2 +- .../Home/View/HomeViewController.swift | 10 ----- 3 files changed, 3 insertions(+), 46 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/Cell/MainCollectionViewCell.swift b/TOASTER-iOS/Present/Home/View/Cell/MainCollectionViewCell.swift index 259a9274..75c4f930 100644 --- a/TOASTER-iOS/Present/Home/View/Cell/MainCollectionViewCell.swift +++ b/TOASTER-iOS/Present/Home/View/Cell/MainCollectionViewCell.swift @@ -9,19 +9,12 @@ import UIKit import SnapKit import Then -protocol MainCollectionViewDelegate: AnyObject { - func searchButtonTapped() -} - // MARK: - main section final class MainCollectionViewCell: UICollectionViewCell { - weak var mainCollectionViewDelegate: MainCollectionViewDelegate? - // MARK: - UI Components - private let searchButton = UIButton() private let userLabel = UILabel() private let noticeLabel = UILabel() private let countToastLabel = UILabel() @@ -76,21 +69,6 @@ extension MainCollectionViewCell { private extension MainCollectionViewCell { func setupStyle() { - searchButton.do { - $0.makeRounded(radius: 12) - $0.setImage(.icSearch20, for: .normal) - $0.setTitle(StringLiterals.Placeholder.search, for: .normal) - $0.setTitleColor(.gray400, for: .normal) - $0.titleLabel?.font = .suitRegular(size: 16) - $0.contentHorizontalAlignment = .left - $0.imageView?.contentMode = .scaleAspectFit - $0.semanticContentAttribute = .forceLeftToRight - var configuration = UIButton.Configuration.filled() - configuration.imagePadding = 8 - configuration.baseBackgroundColor = .gray50 - $0.configuration = configuration - $0.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - } userLabel.do { $0.font = .suitRegular(size: 20) @@ -120,22 +98,15 @@ private extension MainCollectionViewCell { } func setupHierarchy() { - addSubviews(searchButton, - userLabel, + addSubviews(userLabel, noticeLabel, countToastLabel, linkProgressView) } func setupLayout() { - searchButton.snp.makeConstraints { - $0.top.equalToSuperview() - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(42) - } - userLabel.snp.makeConstraints { - $0.top.equalTo(searchButton.snp.bottom).offset(18) + $0.top.equalToSuperview() $0.leading.trailing.equalToSuperview().inset(22) } @@ -155,8 +126,4 @@ private extension MainCollectionViewCell { $0.height.equalTo(12) } } - - @objc func buttonTapped() { - mainCollectionViewDelegate?.searchButtonTapped() - } } diff --git a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift index 1b351fac..0f5f561f 100644 --- a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift +++ b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift @@ -46,7 +46,7 @@ enum CompositionalFactory { // group let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), - heightDimension: .estimated(224)) + heightDimension: .estimated(158)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index 9e5a8251..d9edf9b4 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -111,7 +111,6 @@ extension HomeViewController: UICollectionViewDataSource { ) as? MainCollectionViewCell else { return UICollectionViewCell() } let model = viewModel.mainInfoList cell.bindData(forModel: model) - cell.mainCollectionViewDelegate = self return cell case 1: let lastIndex = viewModel.mainInfoList.mainCategoryListDto.count @@ -372,12 +371,3 @@ extension HomeViewController: UserClipCollectionViewCellDelegate { self.present(addClipBottom, animated: true) } } - -// MARK: - MainCollectionViewDelegate - -extension HomeViewController: MainCollectionViewDelegate { - func searchButtonTapped() { - let searchVC = SearchViewController() - self.navigationController?.pushViewController(searchVC, animated: true) - } -} From f43fbcd27aee982ab00a291c0770b4cf38195c04 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 16 Oct 2024 10:32:26 +0900 Subject: [PATCH 51/98] =?UTF-8?q?[Chore]=20#215=20-=20ClipCollectionHeader?= =?UTF-8?q?View=20search=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Component/ClipCollectionHeaderView.swift | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/TOASTER-iOS/Present/Clip/View/Component/ClipCollectionHeaderView.swift b/TOASTER-iOS/Present/Clip/View/Component/ClipCollectionHeaderView.swift index 4e854a6d..966af163 100644 --- a/TOASTER-iOS/Present/Clip/View/Component/ClipCollectionHeaderView.swift +++ b/TOASTER-iOS/Present/Clip/View/Component/ClipCollectionHeaderView.swift @@ -12,7 +12,6 @@ import Then protocol ClipCollectionHeaderViewDelegate: AnyObject { func addClipButtonTapped() - func searchBarButtonTapped() } final class ClipCollectionHeaderView: UICollectionReusableView { @@ -23,7 +22,6 @@ final class ClipCollectionHeaderView: UICollectionReusableView { // MARK: - UI Components - private let searchBarButton = UIButton() private let clipCountLabel = UILabel() private let addClipButton = UIButton() @@ -47,7 +45,6 @@ final class ClipCollectionHeaderView: UICollectionReusableView { extension ClipCollectionHeaderView { func isDetailClipView(isHidden: Bool) { - searchBarButton.isHidden = isHidden addClipButton.isHidden = isHidden if isHidden { @@ -69,22 +66,6 @@ private extension ClipCollectionHeaderView { func setupStyle() { backgroundColor = .toasterBackground - searchBarButton.do { - $0.makeRounded(radius: 12) - $0.setImage(.icSearch20, for: .normal) - $0.setTitle(StringLiterals.Placeholder.search, for: .normal) - $0.setTitleColor(.gray400, for: .normal) - $0.titleLabel?.font = .suitRegular(size: 16) - $0.contentHorizontalAlignment = .left - $0.imageView?.contentMode = .scaleAspectFit - $0.semanticContentAttribute = .forceLeftToRight - var configuration = UIButton.Configuration.filled() - configuration.imagePadding = 8 - configuration.baseBackgroundColor = .gray50 - $0.configuration = configuration - $0.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - } - clipCountLabel.do { $0.textColor = .gray500 $0.font = .suitBold(size: 12) @@ -101,23 +82,18 @@ private extension ClipCollectionHeaderView { } func setupHierarchy() { - addSubviews(searchBarButton, clipCountLabel, addClipButton) + addSubviews(clipCountLabel, addClipButton) } func setupLayout() { - searchBarButton.snp.makeConstraints { - $0.top.equalToSuperview() - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(42) - } clipCountLabel.snp.makeConstraints { - $0.top.equalTo(searchBarButton.snp.bottom).offset(20) + $0.top.equalToSuperview().inset(4) $0.leading.equalToSuperview().inset(20) } addClipButton.snp.makeConstraints { - $0.top.equalTo(searchBarButton.snp.bottom).offset(15) + $0.top.equalToSuperview() $0.trailing.equalToSuperview().inset(20) } } @@ -125,8 +101,6 @@ private extension ClipCollectionHeaderView { @objc func buttonTapped(_ sender: UIButton) { switch sender { - case searchBarButton: - clipCollectionHeaderViewDelegate?.searchBarButtonTapped() case addClipButton: clipCollectionHeaderViewDelegate?.addClipButtonTapped() default: From c3b61bae61df8747738f08bcbe9f65c218e752c8 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Wed, 16 Oct 2024 17:10:00 +0900 Subject: [PATCH 52/98] =?UTF-8?q?[Feat]=20#220=20-=20Toaster=20Tip=20View?= =?UTF-8?q?=20=EC=9E=AC=EC=82=AC=EC=9A=A9=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ToasterLoadingView.swift | 1 - .../ToasterTipView/ToasterTipView.swift | 147 +++++++++++++++--- 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift b/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift index 53ff1513..ba1a729c 100644 --- a/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift +++ b/TOASTER-iOS/Global/Components/ToasterLoadingView/ToasterLoadingView.swift @@ -122,4 +122,3 @@ private extension ToasterLoadingView { } } } - diff --git a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift index 8452cea5..bbabd74a 100644 --- a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift +++ b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift @@ -13,12 +13,15 @@ final class ToasterTipView: UIView { // MARK: - Properties + /// 현재 툴팁이 보여지고 있는지 여부를 Bool 값으로 반환 + private(set) var isShow: Bool = false + private let title: String private let tipType: TipType // MARK: - UI Components - private let sourceView: UIView + private weak var sourceView: UIView? private let containerView = UIView() private let tipLabel = UILabel() @@ -45,34 +48,111 @@ final class ToasterTipView: UIView { // MARK: - Methods extension ToasterTipView { - /// 툴팁을 보여줄 때 호출하는 함수 - func showTooltip() { + /// 툴팁을 보여줄 때 호출하는 함수 (with 애니메이션) + func showToolTip() { + guard !isShow else { return } + guard let sourceView else { return } + isShow = true + + setupTooltipLayoutBySourceView() + self.alpha = 0 + self.transform = CGAffineTransform(scaleX: 0.2, y: 0.2) + + let finalPosition: CGPoint switch tipType { case .top: - self.snp.makeConstraints { - $0.bottom.equalTo(sourceView.snp.top).offset(-8) - $0.centerX.equalTo(sourceView.snp.centerX) - } + finalPosition = CGPoint( + x: sourceView.center.x, + y: sourceView.frame.minY + ) case .bottom: - self.snp.makeConstraints { - $0.top.equalTo(sourceView.snp.bottom).offset(8) - $0.centerX.equalTo(sourceView.snp.centerX) - } + finalPosition = CGPoint( + x: sourceView.center.x, + y: sourceView.frame.maxY + ) case .left: - self.snp.makeConstraints { - $0.right.equalTo(sourceView.snp.left).offset(-8) - $0.centerY.equalTo(sourceView.snp.centerY) - } + finalPosition = CGPoint( + x: sourceView.frame.minX - self.frame.width / 2, + y: sourceView.center.y + ) case .right: - self.snp.makeConstraints { - $0.left.equalTo(sourceView.snp.right).offset(8) - $0.centerY.equalTo(sourceView.snp.centerY) - } + finalPosition = CGPoint( + x: sourceView.frame.maxX + self.frame.width / 2, + y: sourceView.center.y + ) + } + self.center = CGPoint( + x: sourceView.center.x, + y: sourceView.center.y + ) + UIView.animate( + withDuration: 0.3, + delay: 0, + options: [.curveEaseInOut], + animations: { [weak self] in + guard let self else { return } + self.alpha = 1 + self.transform = CGAffineTransform.identity + self.center = finalPosition + }) + } + + /// 툴팁을 사라지게 할 때 호출하는 함수 (with 애니메이션) + func dismissToolTip(completion: (() -> Void)? = nil) { + UIView.animate( + withDuration: 0.3, + delay: 0, + options: [.curveEaseInOut], + animations: { [weak self] in + guard let self else { return } + guard self.isShow else { return } + + self.isShow = false + self.alpha = 0 + self.transform = CGAffineTransform(scaleX: 0.2, y: 0.2) + + switch self.tipType { + case .top: + self.center = CGPoint( + x: self.sourceView?.center.x ?? 0, + y: self.sourceView?.frame.minY ?? 0 + ) + case .bottom: + self.center = CGPoint( + x: self.sourceView?.center.x ?? 0, + y: self.sourceView?.frame.maxY ?? 0 + ) + case .left: + self.center = CGPoint( + x: self.sourceView?.frame.minX ?? 0, + y: self.sourceView?.center.y ?? 0 + ) + case .right: + self.center = CGPoint( + x: self.sourceView?.frame.maxX ?? 0, + y: self.sourceView?.center.y ?? 0 + ) + } + }, completion: { _ in + self.removeFromSuperview() + completion?() + }) + } + + /// 툴팁을 보여주고, 특정 시간 이후에 자동으로 닫히도록 하는 함수 (with 애니메이션) + func showToolTipAndDismissAfterDelay( + duration: Int, + completion: (() -> Void)? = nil + ) { + showToolTip() + DispatchQueue.main.asyncAfter( + deadline: .now() + .seconds(duration) + ) { [weak self] in + self?.dismissToolTip(completion: completion) } } } - // MARK: - Private Extensions private extension ToasterTipView { @@ -115,4 +195,31 @@ private extension ToasterTipView { $0.leading.trailing.equalToSuperview().inset(10) } } + + func setupTooltipLayoutBySourceView() { + guard let sourceView else { return } + + switch tipType { + case .top: + self.snp.makeConstraints { + $0.bottom.equalTo(sourceView.snp.top).offset(-8) + $0.centerX.equalTo(sourceView.snp.centerX) + } + case .bottom: + self.snp.makeConstraints { + $0.top.equalTo(sourceView.snp.bottom).offset(8) + $0.centerX.equalTo(sourceView.snp.centerX) + } + case .left: + self.snp.makeConstraints { + $0.right.equalTo(sourceView.snp.left).offset(-8) + $0.centerY.equalTo(sourceView.snp.centerY) + } + case .right: + self.snp.makeConstraints { + $0.left.equalTo(sourceView.snp.right).offset(8) + $0.centerY.equalTo(sourceView.snp.centerY) + } + } + } } From 236cd9fe61896971fe3e40fa8667bd3f7c0fe4b8 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Wed, 16 Oct 2024 18:46:26 +0900 Subject: [PATCH 53/98] =?UTF-8?q?[Feat]=20#220=20-=20ToolBarItem,=20TabBar?= =?UTF-8?q?Item=EC=97=90=20=EB=8C=80=ED=95=B4=EC=84=9C=EB=8F=84=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C?= =?UTF-8?q?=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 --- .../ToasterTipView/ToasterTipView.swift | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift index bbabd74a..92a9f163 100644 --- a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift +++ b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift @@ -29,10 +29,10 @@ final class ToasterTipView: UIView { // MARK: - Life Cycles - init(title: String, type: TipType, sourceItem: UIView) { + init(title: String, type: TipType, sourceItem: AnyObject) { self.title = title self.tipType = type - self.sourceView = sourceItem + self.sourceView = (sourceItem as? UIView) ?? sourceItem.view super.init(frame: .zero) setupStyle() setupHierarchy() @@ -106,33 +106,8 @@ extension ToasterTipView { animations: { [weak self] in guard let self else { return } guard self.isShow else { return } - self.isShow = false self.alpha = 0 - self.transform = CGAffineTransform(scaleX: 0.2, y: 0.2) - - switch self.tipType { - case .top: - self.center = CGPoint( - x: self.sourceView?.center.x ?? 0, - y: self.sourceView?.frame.minY ?? 0 - ) - case .bottom: - self.center = CGPoint( - x: self.sourceView?.center.x ?? 0, - y: self.sourceView?.frame.maxY ?? 0 - ) - case .left: - self.center = CGPoint( - x: self.sourceView?.frame.minX ?? 0, - y: self.sourceView?.center.y ?? 0 - ) - case .right: - self.center = CGPoint( - x: self.sourceView?.frame.maxX ?? 0, - y: self.sourceView?.center.y ?? 0 - ) - } }, completion: { _ in self.removeFromSuperview() completion?() From c90f15b5971e8a713fa9fd0f20342dd2df52646b Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 16 Oct 2024 22:45:29 +0900 Subject: [PATCH 54/98] =?UTF-8?q?[Chore]=20#215=20-=20ClipView=20search=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Present/Clip/View/ClipViewController.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/TOASTER-iOS/Present/Clip/View/ClipViewController.swift b/TOASTER-iOS/Present/Clip/View/ClipViewController.swift index 9739b117..b57e5dae 100644 --- a/TOASTER-iOS/Present/Clip/View/ClipViewController.swift +++ b/TOASTER-iOS/Present/Clip/View/ClipViewController.swift @@ -197,17 +197,11 @@ extension ClipViewController: UICollectionViewDelegateFlowLayout { // referenceSizeForHeaderInSection: 각 섹션의 헤더 뷰 크기를 CGSize 형태로 return func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { - return CGSize(width: collectionView.frame.width, height: 90) + return CGSize(width: collectionView.frame.width, height: 33) } } extension ClipViewController: ClipCollectionHeaderViewDelegate { - func searchBarButtonTapped() { - let searchVC = SearchViewController() - searchVC.hidesBottomBarWhenPushed = true - navigationController?.pushViewController(searchVC, animated: true) - } - func addClipButtonTapped() { if viewModel.clipList.clips.count >= 15 { showToastMessage(width: 243, status: .warning, message: StringLiterals.ToastMessage.noticeMaxClip) From 691c8cf648f0c317b72bb5244243762bd6d70f1b Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Wed, 16 Oct 2024 22:47:04 +0900 Subject: [PATCH 55/98] =?UTF-8?q?[Feat]=20#220=20-=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=BD=94=EC=B9=98=EB=A7=88=ED=81=AC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LinkWeb/View/LinkWebNavigationView.swift | 2 +- .../LinkWeb/View/LinkWebToolBarView.swift | 2 +- .../LinkWebViewController.swift | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift index 7b2bde70..84d631a2 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebNavigationView.swift @@ -20,7 +20,7 @@ final class LinkWebNavigationView: UIView { // MARK: - UI Components private let popButton = UIButton() - private let addressLabel = UITextField() + private(set) var addressLabel = UITextField() private let reloadButton = UIButton() // MARK: - Life Cycles diff --git a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift index 05336ad7..ca7a4f9b 100644 --- a/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift +++ b/TOASTER-iOS/Present/LinkWeb/View/LinkWebToolBarView.swift @@ -48,7 +48,7 @@ final class LinkWebToolBarView: UIView { private let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) private let backButton = UIBarButtonItem() private let forwardButton = UIBarButtonItem() - private let readLinkCheckButton = UIBarButtonItem() + private(set) var readLinkCheckButton = UIBarButtonItem() private let shareLinkButton = UIBarButtonItem() private let safariButton = UIBarButtonItem() diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 1c2c797a..67e205d3 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -28,6 +28,18 @@ final class LinkWebViewController: UIViewController { private let webView = WKWebView() private let toolBar = LinkWebToolBarView() + private lazy var firstToolTip = ToasterTipView( + title: "직접 복사할 수 있어요", + type: .bottom, + sourceItem: navigationView.addressLabel + ) + + private lazy var secondToolTip = ToasterTipView( + title: "열람 버튼을 클릭해보세요!", + type: .top, + sourceItem: toolBar.readLinkCheckButton + ) + // MARK: - Life Cycle override func viewDidLoad() { @@ -53,6 +65,19 @@ final class LinkWebViewController: UIViewController { showNavigationBar() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + DispatchQueue.main.asyncAfter(deadline: .now()+3) { + self.view.addSubview(self.secondToolTip) + self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 3) { + self.view.addSubview(self.firstToolTip) + self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 3) + } + } + + } + deinit { progressObservation?.invalidate() } From 8ea9dc4cef450e5a541a7caa4f3016fdd822f190 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 17:22:30 +0900 Subject: [PATCH 56/98] =?UTF-8?q?[Chore]=20#215=20-=20Second=20Header=20ti?= =?UTF-8?q?tle=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/Home/View/Cell/HomeHeaderCollectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift b/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift index 4257bdbd..5602adfe 100644 --- a/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift +++ b/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift @@ -62,7 +62,7 @@ final class HomeHeaderCollectionView: UICollectionReusableView { extension HomeHeaderCollectionView { func configureHeader(forTitle: String, num: Int) { if num == 1 { - titleLabel.text = forTitle + "님의 클립" + titleLabel.text = forTitle + "님이 최근 저장한 링크" titleLabel.font = .suitMedium(size: 18) titleLabel.asFont(targetString: forTitle, font: .suitBold(size: 18)) } else { From 6934489c98c23f0a77a7c45903140096f720f7bb Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 17:22:46 +0900 Subject: [PATCH 57/98] =?UTF-8?q?[Chore]=20#215=20-=20Compositional=20Layo?= =?UTF-8?q?ut=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Component/CompositioinalFactory.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift index 0f5f561f..8033de53 100644 --- a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift +++ b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift @@ -69,12 +69,12 @@ enum CompositionalFactory { // MARK: - User Clip 에 대한 Layout static func createUserClipSection() -> NSCollectionLayoutSection { - let itemFractionalWidthFraction = 1.0 / 2.0 - let itemInset: CGFloat = 7 + let itemFractionalWidthFraction = 1.0 / 1.0 + let itemInset: CGFloat = 8 // item let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(itemFractionalWidthFraction), - heightDimension: .absolute(150)) + heightDimension: .absolute(98)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: itemInset, leading: itemInset, @@ -83,7 +83,7 @@ enum CompositionalFactory { // group let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), - heightDimension: .absolute(150)) + heightDimension: .absolute(98)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) From 94eb91017de13ba5e5b7c4810d45b7e51c2d1cff Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 18:28:20 +0900 Subject: [PATCH 58/98] [Add] #215 - Recent Link Model, DTO --- TOASTER-iOS.xcodeproj/project.pbxproj | 8 ++++++++ .../Toaster/DTO/Response/GetRecentLinkResponseDTO.swift | 8 ++++++++ TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift | 8 ++++++++ 3 files changed, 24 insertions(+) create mode 100644 TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift create mode 100644 TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 8e8b2a8d..da89206f 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -292,6 +292,8 @@ 83CFC3372B564F1100A2EB2B /* WeeklyLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83CFC3362B564F1100A2EB2B /* WeeklyLinkModel.swift */; }; 83CFC3392B568BE700A2EB2B /* RecommendSiteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83CFC3382B568BE700A2EB2B /* RecommendSiteModel.swift */; }; 83CFC33B2B57324700A2EB2B /* SaveLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83CFC33A2B57324700A2EB2B /* SaveLinkModel.swift */; }; + 83D80DCC2CC1059000DD5410 /* RecentLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D80DCB2CC1059000DD5410 /* RecentLinkModel.swift */; }; + 83D80DD02CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D80DCF2CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -510,6 +512,8 @@ 83CFC3362B564F1100A2EB2B /* WeeklyLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeeklyLinkModel.swift; sourceTree = ""; }; 83CFC3382B568BE700A2EB2B /* RecommendSiteModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendSiteModel.swift; sourceTree = ""; }; 83CFC33A2B57324700A2EB2B /* SaveLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveLinkModel.swift; sourceTree = ""; }; + 83D80DCB2CC1059000DD5410 /* RecentLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentLinkModel.swift; sourceTree = ""; }; + 83D80DCF2CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetRecentLinkResponseDTO.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1436,6 +1440,7 @@ 6BE6DA872B546B24008B06FA /* PatchOpenLinkResponseDTO.swift */, 6BE6DA8B2B546CA9008B06FA /* GetWeeksLinkResponseDTO.swift */, 8364220B2BE7BFB2005C4085 /* PatchEditLinkTitleResponseDTO.swift */, + 83D80DCF2CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift */, ); path = Response; sourceTree = ""; @@ -1545,6 +1550,7 @@ 83CFC3362B564F1100A2EB2B /* WeeklyLinkModel.swift */, 83CFC3382B568BE700A2EB2B /* RecommendSiteModel.swift */, 396DCE022CA26C6600FEF7C8 /* PopupInfoModel.swift */, + 83D80DCB2CC1059000DD5410 /* RecentLinkModel.swift */, ); path = Model; sourceTree = ""; @@ -1901,6 +1907,7 @@ 6BE6DAA42B547579008B06FA /* GetDetailTimerResponseDTO.swift in Sources */, 6BE6D9E22B4E9B58008B06FA /* CompleteTimerCollectionViewCell.swift in Sources */, 8334CFA02CA6E2D200319922 /* ViewModelType.swift in Sources */, + 83D80DD02CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift in Sources */, 3F2FA1792B45C46F00EDBF95 /* KakaoAuthenticateAdapter.swift in Sources */, 6BE6DA342B50594B008B06FA /* MoyaPlugin.swift in Sources */, 396DCDFA2CA19F2000FEF7C8 /* PopupTargetType.swift in Sources */, @@ -2021,6 +2028,7 @@ 391908422B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift in Sources */, 6BE6DA732B50C33A008B06FA /* GetAllCategoryResponseDTO.swift in Sources */, 3F2FA1752B45C0AF00EDBF95 /* Config.swift in Sources */, + 83D80DCC2CC1059000DD5410 /* RecentLinkModel.swift in Sources */, 6B6AE6AC2B3FF6F7000E2366 /* UIColor+.swift in Sources */, 6BC493682B45D7B100544249 /* ToasterNavigationController.swift in Sources */, 6BE6DA492B50ADC2008B06FA /* NetworkService.swift in Sources */, diff --git a/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift b/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift new file mode 100644 index 00000000..0ff05ecc --- /dev/null +++ b/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift @@ -0,0 +1,8 @@ +// +// GetRecentLinkResponseDTO.swift +// TOASTER-iOS +// +// Created by Gahyun Kim on 10/17/24. +// + +import Foundation diff --git a/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift b/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift new file mode 100644 index 00000000..36cc9993 --- /dev/null +++ b/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift @@ -0,0 +1,8 @@ +// +// RecentLinkModel.swift +// TOASTER-iOS +// +// Created by Gahyun Kim on 10/17/24. +// + +import Foundation From 9d2afd34be4dab17c0ac88a3e51c126e88393db0 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 18:28:54 +0900 Subject: [PATCH 59/98] =?UTF-8?q?[Feat]=20#215=20-=20ToasterTargetType?= =?UTF-8?q?=EC=97=90=20GetRecentLink=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS/Network/Toaster/ToasterTargetType.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift b/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift index 8c59b0a1..55d85190 100644 --- a/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift +++ b/TOASTER-iOS/Network/Toaster/ToasterTargetType.swift @@ -15,6 +15,7 @@ enum ToasterTargetType { case deleteLink(toastId: Int) case getWeeksLink case patchEditLinkTitle(requestBody: PatchEditLinkTitleRequestDTO) + case getRecentLink } extension ToasterTargetType: BaseTargetType { @@ -46,6 +47,7 @@ extension ToasterTargetType: BaseTargetType { case .deleteLink: return utilPath.rawValue + "/delete" case .getWeeksLink: return utilPath.rawValue + "/week" case .patchEditLinkTitle: return utilPath.rawValue + "/title" + case .getRecentLink: return utilPath.rawValue + "/recent-saved" } } @@ -56,6 +58,7 @@ extension ToasterTargetType: BaseTargetType { case .deleteLink: return .delete case .getWeeksLink: return .get case .patchEditLinkTitle: return .patch + case .getRecentLink: return .get } } } From 1681ec2e2d9d844689c9b4101e41747a1b19f79d Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 18:29:39 +0900 Subject: [PATCH 60/98] =?UTF-8?q?[Feat]=20#215=20-=20RecentLinkModel,=20DT?= =?UTF-8?q?O=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTO/Response/GetRecentLinkResponseDTO.swift | 15 +++++++++++++++ .../Present/Home/Model/RecentLinkModel.swift | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift b/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift index 0ff05ecc..df8d2f3a 100644 --- a/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift +++ b/TOASTER-iOS/Network/Toaster/DTO/Response/GetRecentLinkResponseDTO.swift @@ -6,3 +6,18 @@ // import Foundation + +struct GetRecentLinkResponseDTO: Codable { + let code: Int + let message: String + let data: [GetRecentLinkResponseData] +} + +struct GetRecentLinkResponseData: Codable { + let toastId: Int + let toastTitle: String + let linkUrl: String + let isRead: Bool + let categoryTitle: String? + let thumbnailUrl: String? +} diff --git a/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift b/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift index 36cc9993..4ff02761 100644 --- a/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift +++ b/TOASTER-iOS/Present/Home/Model/RecentLinkModel.swift @@ -6,3 +6,12 @@ // import Foundation + +struct RecentLinkModel { + let toastId: Int + let toastTitle: String + let linkUrl: String + let isRead: Bool + let categoryTitle: String? + let thumbnailUrl: String? +} From 2cf49ae91563ec65b97489df3cf4b9af6051e76d Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 19:51:48 +0900 Subject: [PATCH 61/98] =?UTF-8?q?[Feat]=20#215=20-=20RecentLink=20API=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/Toaster/ToasterAPIService.swift | 17 +++++++++ .../Home/ViewModel/HomeViewModel.swift | 38 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift b/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift index 684165f7..9359ca2d 100644 --- a/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift +++ b/TOASTER-iOS/Network/Toaster/ToasterAPIService.swift @@ -19,6 +19,7 @@ protocol ToasterAPIServiceProtocol { func getWeeksLink(completion: @escaping (NetworkResult) -> Void) func patchEditLinkTitle(requestBody: PatchEditLinkTitleRequestDTO, completion: @escaping (NetworkResult) -> Void) + func getRecentLink(completion: @escaping (NetworkResult) -> Void) } final class ToasterAPIService: BaseAPIService, ToasterAPIServiceProtocol { @@ -108,4 +109,20 @@ final class ToasterAPIService: BaseAPIService, ToasterAPIServiceProtocol { } } } + + func getRecentLink(completion: @escaping (NetworkResult) -> Void) { + provider.request(.getRecentLink) { result in + switch result { + case .success(let response): + let networkResult: NetworkResult = self.fetchNetworkResult(statusCode: response.statusCode, data: response.data) + print(networkResult.stateDescription) + completion(networkResult) + case .failure(let error): + if let response = error.response { + let networkResult: NetworkResult = self.fetchNetworkResult(statusCode: response.statusCode, data: response.data) + completion(networkResult) + } + } + } + } } diff --git a/TOASTER-iOS/Present/Home/ViewModel/HomeViewModel.swift b/TOASTER-iOS/Present/Home/ViewModel/HomeViewModel.swift index a2cbdc72..e8642faf 100644 --- a/TOASTER-iOS/Present/Home/ViewModel/HomeViewModel.swift +++ b/TOASTER-iOS/Present/Home/ViewModel/HomeViewModel.swift @@ -32,6 +32,19 @@ final class HomeViewModel { } } + private(set) var recentLink: [RecentLinkModel] = [ + RecentLinkModel(toastId: 0, + toastTitle: "", + linkUrl: "", + isRead: true, + categoryTitle: nil ?? "", + thumbnailUrl: nil ?? "") + ] { + didSet { + dataChangeAction?(!recentLink.isEmpty) + } + } + private(set) var weeklyLinkList: [WeeklyLinkModel] = [ WeeklyLinkModel(toastId: 0, toastTitle: "", @@ -221,4 +234,29 @@ extension HomeViewModel { } } } + + // 최근 링크 -> GET + func fetchRecentLinkData() { + NetworkService.shared.toastService.getRecentLink { result in + switch result { + case .success(let response): + var list: [RecentLinkModel] = [] + if let data = response?.data { + for idx in 0.. Date: Thu, 17 Oct 2024 19:52:36 +0900 Subject: [PATCH 62/98] [Feat] #215 - Configure cell RecentLink --- TOASTER-iOS.xcodeproj/project.pbxproj | 2 ++ .../DetailClipListCollectionViewCell.swift | 23 ++++++++++++++ .../Home/View/HomeViewController.swift | 31 ++++++++----------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index da89206f..dc00f278 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ 83CFC33B2B57324700A2EB2B /* SaveLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83CFC33A2B57324700A2EB2B /* SaveLinkModel.swift */; }; 83D80DCC2CC1059000DD5410 /* RecentLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D80DCB2CC1059000DD5410 /* RecentLinkModel.swift */; }; 83D80DD02CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D80DCF2CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift */; }; + 83D80DD12CC1113E00DD5410 /* GetRecentLinkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D80DCF2CC10D8C00DD5410 /* GetRecentLinkResponseDTO.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1895,6 +1896,7 @@ 3F3ED27C2BA1A29F004E79F0 /* BaseAPIService.swift in Sources */, 3F3ED2B32BA1D59D004E79F0 /* ClipListCollectionViewCell.swift in Sources */, 3F3ED2B72BA1D897004E79F0 /* SearchResultModel.swift in Sources */, + 83D80DD12CC1113E00DD5410 /* GetRecentLinkResponseDTO.swift in Sources */, 3F3ED2812BA1A3F5004E79F0 /* ClipAPIService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift b/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift index fa528dd4..b6895501 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift @@ -92,6 +92,29 @@ extension DetailClipListCollectionViewCell { } } + func configureCell(forModel: RecentLinkModel, isClipHidden: Bool) { + modifiedButton.isHidden = false + clipNameLabel.text = forModel.toastTitle + linkTitleLabel.text = forModel.toastTitle + linkLabel.text = forModel.linkUrl + isClipNameLabelHidden = forModel.categoryTitle != nil ? true : false + isReadDimmedView = forModel.isRead + toastId = forModel.toastId + + if forModel.categoryTitle != nil && !isClipHidden { + clipNameLabel.text = forModel.categoryTitle + isClipNameLabelHidden = false + } else { + isClipNameLabelHidden = true + } + + if let imageURL = forModel.thumbnailUrl { + linkImage.kf.setImage(with: URL(string: imageURL)) + } else { + linkImage.image = .imgThumbnail + } + } + func configureCell(forModel: SearchResultDetailClipModel, forText: String) { modifiedButton.isHidden = true linkTitleLabel.text = forModel.title diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index d9edf9b4..9ffce598 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -41,6 +41,7 @@ final class HomeViewController: UIViewController { viewModel.fetchWeeklyLinkData() viewModel.fetchRecommendSiteData() viewModel.getPopupInfoAPI() + viewModel.fetchRecentLinkData() } } @@ -91,8 +92,8 @@ extension HomeViewController: UICollectionViewDataSource { case 0: return 1 case 1: - let count = viewModel.mainInfoList.mainCategoryListDto.count - return min(count + 1, 4) + let count = viewModel.recentLink.count + return min(count + 1, 3) case 2: return viewModel.weeklyLinkList.count case 3: @@ -113,26 +114,20 @@ extension HomeViewController: UICollectionViewDataSource { cell.bindData(forModel: model) return cell case 1: - let lastIndex = viewModel.mainInfoList.mainCategoryListDto.count - if indexPath.item == lastIndex && lastIndex < 4 { + let lastIndex = viewModel.recentLink.count + if indexPath.item == lastIndex && lastIndex < 3 { guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: UserClipEmptyCollectionViewCell.className, + withReuseIdentifier: DetailClipListCollectionViewCell.className, for: indexPath - ) as? UserClipEmptyCollectionViewCell else { return UICollectionViewCell() } + ) as? DetailClipListCollectionViewCell else { return UICollectionViewCell() } return cell } else { guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: UserClipCollectionViewCell.className, + withReuseIdentifier: DetailClipListCollectionViewCell.className, for: indexPath - ) as? UserClipCollectionViewCell else { return UICollectionViewCell() } - let model = viewModel.mainInfoList.mainCategoryListDto - if indexPath.item == 0 { - cell.bindData(forModel: model[indexPath.item], - icon: .icAllClip24.withTintColor(.black900)) - } else { - cell.bindData(forModel: model[indexPath.item], - icon: .icClipFull24) - } + ) as? DetailClipListCollectionViewCell else { return UICollectionViewCell() } + let model = viewModel.recentLink + cell.configureCell(forModel: model[indexPath.item], isClipHidden: false) return cell } case 2: @@ -235,8 +230,8 @@ private extension HomeViewController { forCellWithReuseIdentifier: WeeklyLinkCollectionViewCell.className) $0.register(WeeklyRecommendCollectionViewCell.self, forCellWithReuseIdentifier: WeeklyRecommendCollectionViewCell.className) - $0.register(UserClipEmptyCollectionViewCell.self, - forCellWithReuseIdentifier: UserClipEmptyCollectionViewCell.className) + $0.register(DetailClipListCollectionViewCell.self, + forCellWithReuseIdentifier: DetailClipListCollectionViewCell.className) // header $0.register(HomeHeaderCollectionView.self, From bf28f4d5aba8070003c7c3f69ae6fecca9d24326 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 22:13:11 +0900 Subject: [PATCH 63/98] =?UTF-8?q?[Chore]=20#215=20-=20Cell=20layout=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Component/CompositioinalFactory.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift index 8033de53..48fabfcc 100644 --- a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift +++ b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift @@ -74,16 +74,16 @@ enum CompositionalFactory { // item let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(itemFractionalWidthFraction), - heightDimension: .absolute(98)) + heightDimension: .absolute(104)) let item = NSCollectionLayoutItem(layoutSize: itemSize) - item.contentInsets = NSDirectionalEdgeInsets(top: itemInset, + item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: itemInset, bottom: itemInset, trailing: itemInset) // group let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), - heightDimension: .absolute(98)) + heightDimension: .absolute(104)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) @@ -101,7 +101,7 @@ enum CompositionalFactory { elementKind: UICollectionView.elementKindSectionHeader, alignment: .top), NSCollectionLayoutBoundarySupplementaryItem(layoutSize: .init(widthDimension: .fractionalWidth(1), - heightDimension: .absolute(17)), + heightDimension: .absolute(9)), elementKind: UICollectionView.elementKindSectionFooter, alignment: .bottom)] return section From e24e9457dd481f53daea72cefddb1c0a4210e630 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Thu, 17 Oct 2024 22:19:05 +0900 Subject: [PATCH 64/98] =?UTF-8?q?[Chore]=20#215=20-=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/Home/View/Component/CompositioinalFactory.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift index 48fabfcc..41eb8d5f 100644 --- a/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift +++ b/TOASTER-iOS/Present/Home/View/Component/CompositioinalFactory.swift @@ -17,7 +17,7 @@ enum CompositionalFactory { case 0: section = createMainSection() case 1: - section = createUserClipSection() + section = createUserRecentLinkSection() case 2: section = createWeeklyLinkSection() case 3: @@ -68,7 +68,7 @@ enum CompositionalFactory { // MARK: - User Clip 에 대한 Layout - static func createUserClipSection() -> NSCollectionLayoutSection { + static func createUserRecentLinkSection() -> NSCollectionLayoutSection { let itemFractionalWidthFraction = 1.0 / 1.0 let itemInset: CGFloat = 8 From b861a71ab09e00e5cb1cd14d87723dc6ec75d263 Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 24 Oct 2024 01:33:04 +0900 Subject: [PATCH 65/98] =?UTF-8?q?[Fix]=20#217=20-=20=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20Rem?= =?UTF-8?q?indSelectClipCell=20UI=20=EB=A1=9C=EC=A7=81=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 --- .../RemindSelectClipCollectionViewCell.swift | 53 +++++++++++++++---- .../ShareViewController.swift | 4 +- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift b/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift index ff4112e2..f0d7ff24 100644 --- a/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift +++ b/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift @@ -10,21 +10,31 @@ import UIKit import SnapKit import Then +enum ClipCellType { + case remind + case shareExtension + case chagneClip +} + final class RemindSelectClipCollectionViewCell: UICollectionViewCell { // MARK: - Properties + + private var currentCategoryTitle: String? override var isSelected: Bool { didSet { - if isSelected { - setupSelected() - } else { - setupDeselected() + if clipTitleLabel.text != currentCategoryTitle { + if isSelected { + setupSelected() + } else { + setupDeselected() + } } } } - var isShareExtension = false { + var isRounded = true { didSet { updateRoundedStyle() } @@ -67,11 +77,34 @@ extension RemindSelectClipCollectionViewCell { clipImageView.image = isSelected == true ? icon.withTintColor(.toasterPrimary) : icon } - func configureCell(forModel: RemindClipModel, icon: UIImage, isShareExtension: Bool) { + func configureCell(forModel: RemindClipModel, icon: UIImage, isRounded: Bool) { clipTitleLabel.text = forModel.title clipCountLabel.text = "\(forModel.clipCount)개" clipImageView.image = isSelected == true ? icon.withTintColor(.toasterPrimary) : icon - self.isShareExtension = isShareExtension + self.isRounded = isRounded + } + + func configureCurrentClipCell(forModel: SelectClipModel, icon: UIImage) { + currentCategoryTitle = forModel.title + clipTitleLabel.text = forModel.title + clipCountLabel.text = "\(forModel.clipCount)개" + clipImageView.image = icon + + clipTitleLabel.textColor = .gray200 + clipCountLabel.textColor = .gray200 + + self.isRounded = false + } + + func configureChnageClipCell(forModel: SelectClipModel, icon: UIImage) { + clipTitleLabel.text = forModel.title + clipCountLabel.text = "\(forModel.clipCount)개" + clipImageView.image = icon + + clipTitleLabel.textColor = .black850 + clipCountLabel.textColor = .gray600 + + self.isRounded = false } } @@ -139,10 +172,10 @@ private extension RemindSelectClipCollectionViewCell { } func updateRoundedStyle() { - if isShareExtension { - makeRounded(radius: 0) - } else { + if isRounded { makeRounded(radius: 12) + } else { + makeRounded(radius: 0) } } } diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 1f9e2d2c..e9309626 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -311,9 +311,9 @@ extension ShareViewController: UICollectionViewDataSource { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RemindSelectClipCollectionViewCell.className, for: indexPath) as? RemindSelectClipCollectionViewCell else { return UICollectionViewCell() } if indexPath.item == 0 { - cell.configureCell(forModel: viewModel.clipData[indexPath.item], icon: .icAllClip24, isShareExtension: true) + cell.configureCell(forModel: viewModel.clipData[indexPath.item], icon: .icAllClip24, isRounded: false) } else { - cell.configureCell(forModel: viewModel.clipData[indexPath.item], icon: .icClip24Black, isShareExtension: true) + cell.configureCell(forModel: viewModel.clipData[indexPath.item], icon: .icClip24Black, isRounded: false) } return cell From efd99b5279d2787effb6d637bfe367ce1a09ad6d Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 24 Oct 2024 01:34:18 +0900 Subject: [PATCH 66/98] =?UTF-8?q?[Feat]=20#217=20-=20=ED=81=B4=EB=A6=BD=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B4=80=EB=A0=A8=20UI=20=EB=B0=8F=20Comb?= =?UTF-8?q?ine=20=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PatchChangeCategoryResponseDTO.swift | 8 +- .../Component/ChangeClipBottomSheetView.swift | 181 ++++++++++++++++++ ....swift => LinkOptionBottomSheetView.swift} | 41 +++- .../View/DetailClipViewController.swift | 105 +++++++++- .../ViewModel/ChangeClipViewModel.swift | 169 ++++++++++++++++ 5 files changed, 486 insertions(+), 18 deletions(-) create mode 100644 TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift rename TOASTER-iOS/Present/DetailClip/View/Component/{DeleteLinkBottomSheetView.swift => LinkOptionBottomSheetView.swift} (72%) create mode 100644 TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift diff --git a/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift b/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift index 7776b28d..9b0fcf2d 100644 --- a/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift +++ b/TOASTER-iOS/Network/Toaster/DTO/Response/PatchChangeCategoryResponseDTO.swift @@ -7,6 +7,12 @@ import Foundation -struct PatchChangeCategoryResponseDTO: Decodable { +struct PatchChangeCategoryResponseDTO: Codable { + let code: Int + let message: String + let data: PatchChangeCategoryResponseData +} + +struct PatchChangeCategoryResponseData: Codable { let categoryId: Int } diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift new file mode 100644 index 00000000..1e7f0e78 --- /dev/null +++ b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift @@ -0,0 +1,181 @@ +// +// ChangeClipBottomSheetView.swift +// TOASTER-iOS +// +// Created by ParkJunHyuk on 10/9/24. +// + +import UIKit + +import SnapKit +import Then + +final class ChangeClipBottomSheetView: UIView { + + // MARK: - Properties + + var dataSourceHandler: (() -> [SelectClipModel])? + + weak var delegate: ChangeClipBottomSheetViewDelegate? + + // MARK: - UI Components + + private var clipSelectCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + private let completeBottomButton = UIButton() + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + + setupStyle() + setupHierarchy() + setupLayout() + setupRegisterCell() + setupDelegate() + setupAddTarget() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func reloadChangeClipBottom() { + clipSelectCollectionView.reloadData() + } + + /// 버튼 활성화에 따른 UI 변경 + func updateCompleteButtonUI(_ isEnable: Bool) { + if isEnable == true { + completeBottomButton.isUserInteractionEnabled = true + completeBottomButton.backgroundColor = .black850 + } else { + completeBottomButton.isUserInteractionEnabled = false + completeBottomButton.backgroundColor = .gray200 + } + } +} + +// MARK: - Private Extensions + +private extension ChangeClipBottomSheetView { + func setupStyle() { + backgroundColor = .gray50 + + clipSelectCollectionView.do { + $0.backgroundColor = .gray50 + $0.makeRounded(radius: 12) + $0.clipsToBounds = true + $0.isScrollEnabled = true + $0.showsVerticalScrollIndicator = false + } + + completeBottomButton.do { + $0.setTitle(StringLiterals.Button.complete, for: .normal) + $0.setTitleColor(.toasterWhite, for: .normal) + $0.backgroundColor = .gray200 + $0.makeRounded(radius: 12) + } + } + + func setupHierarchy() { + addSubviews(clipSelectCollectionView, completeBottomButton) + } + + func setupLayout() { + clipSelectCollectionView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview().inset(20) + $0.bottom.equalTo(completeBottomButton.snp.top).offset(-20) + } + + completeBottomButton.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.horizontalEdges.equalToSuperview().inset(20) + $0.bottom.equalToSuperview().inset(34) + $0.height.equalTo(62) + } + } + + func setupRegisterCell() { + clipSelectCollectionView.register(RemindSelectClipCollectionViewCell.self, forCellWithReuseIdentifier: RemindSelectClipCollectionViewCell.className) + } + + func setupDelegate() { + clipSelectCollectionView.delegate = self + clipSelectCollectionView.dataSource = self + } + + func setupAddTarget() { + completeBottomButton.addTarget(self, action: #selector(completeBottomuttonTapped), for: .touchUpInside) + } + + @objc func completeBottomuttonTapped(_ sender: UIButton) { + delegate?.completButtonTap() + } +} + +// MARK: - CollectionView DataSource + +extension ChangeClipBottomSheetView: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataSourceHandler?().count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RemindSelectClipCollectionViewCell.className, for: indexPath) as? RemindSelectClipCollectionViewCell, let clipData = dataSourceHandler?() else { return UICollectionViewCell() } + + if indexPath.row == 0 { + cell.configureCurrentClipCell(forModel: clipData[indexPath.item], icon: .icClip24) + } else { + cell.configureChnageClipCell(forModel: clipData[indexPath.item], icon: .icClip24Black) + } + + return cell + } +} + +// MARK: - CollectionViewDelegate + +extension ChangeClipBottomSheetView: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + return indexPath.item != 0 // 첫 번째 아이템(인덱스 0)이 아닌 경우에만 true 반환 + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let clipData = dataSourceHandler?() else { return } + + if indexPath.item != 0 { + delegate?.didSelectClip(selectClipId: clipData[indexPath.row].id) + + if let cell = collectionView.cellForItem(at: .SubSequence(item: 0, section: 0)) { + cell.isSelected = false + } + } + } +} + +// MARK: - UICollectionViewDelegateFlowLayout + +extension ChangeClipBottomSheetView: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: convertByWidthRatio(335), height: 54) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 1 + } +} + +protocol ChangeClipBottomSheetViewDelegate: AnyObject { + func didSelectClip(selectClipId: Int) + func completButtonTap() +} diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/DeleteLinkBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift similarity index 72% rename from TOASTER-iOS/Present/DetailClip/View/Component/DeleteLinkBottomSheetView.swift rename to TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift index db169bfb..c6439d4f 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/DeleteLinkBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift @@ -10,20 +10,23 @@ import UIKit import SnapKit import Then -final class DeleteLinkBottomSheetView: UIView { +final class LinkOptionBottomSheetView: UIView { // MARK: - Properties private var deleteLinkBottomSheetViewButtonAction: (() -> Void)? private var editLinkTitleBottomSheetViewButtonAction: (() -> Void)? private var confirmBottomSheetViewButtonAction: (() -> Void)? + private var changeClipBottomSheetViewButtonAction: (() -> Void)? // MARK: - UI Components private let deleteButton = UIButton() private let editButton = UIButton() + private let changeClipButton = UIButton() private let deleteButtonLabel = UILabel() private let editButtonLabel = UILabel() + private let changeClipButtonLabel = UILabel() // MARK: - Life Cycles @@ -44,7 +47,7 @@ final class DeleteLinkBottomSheetView: UIView { // MARK: - Extensions -extension DeleteLinkBottomSheetView { +extension LinkOptionBottomSheetView { func setupDeleteLinkBottomSheetButtonAction(_ action: (() -> Void)?) { deleteLinkBottomSheetViewButtonAction = action } @@ -56,11 +59,15 @@ extension DeleteLinkBottomSheetView { func setupConfirmBottomSheetButtonAction(_ action: (() -> Void)?) { confirmBottomSheetViewButtonAction = action } + + func setupChangeClipBottomSheetButtonAction(_ action: (() -> Void)?) { + changeClipBottomSheetViewButtonAction = action + } } // MARK: - Private Extensions -private extension DeleteLinkBottomSheetView { +private extension LinkOptionBottomSheetView { func setupStyle() { backgroundColor = .gray50 @@ -76,8 +83,12 @@ private extension DeleteLinkBottomSheetView { $0.makeRounded(radius: 12) } + changeClipButton.do { + $0.backgroundColor = .toasterWhite + } + editButtonLabel.do { - $0.text = "수정하기" + $0.text = "제목 편집" $0.textColor = .black900 $0.font = .suitMedium(size: 16) } @@ -87,11 +98,18 @@ private extension DeleteLinkBottomSheetView { $0.textColor = .toasterError $0.font = .suitMedium(size: 16) } + + changeClipButtonLabel.do { + $0.text = "클립 이동" + $0.textColor = .black900 + $0.font = .suitMedium(size: 16) + } } func setupHierarchy() { - addSubviews(editButton, deleteButton) + addSubviews(editButton, changeClipButton, deleteButton) editButton.addSubview(editButtonLabel) + changeClipButton.addSubview(changeClipButtonLabel) deleteButton.addSubview(deleteButtonLabel) } @@ -102,13 +120,19 @@ private extension DeleteLinkBottomSheetView { $0.height.equalTo(54) } - deleteButton.snp.makeConstraints { + changeClipButton.snp.makeConstraints { $0.leading.trailing.equalToSuperview().inset(20) $0.top.equalTo(editButton.snp.bottom).offset(1) $0.height.equalTo(54) } - [editButtonLabel, deleteButtonLabel].forEach { + deleteButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(20) + $0.top.equalTo(changeClipButton.snp.bottom).offset(1) + $0.height.equalTo(54) + } + + [editButtonLabel, changeClipButtonLabel, deleteButtonLabel].forEach { $0.snp.makeConstraints { $0.centerY.equalToSuperview() $0.leading.equalToSuperview().inset(20) @@ -119,6 +143,7 @@ private extension DeleteLinkBottomSheetView { func setupAddTarget() { editButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) deleteButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + changeClipButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } @objc @@ -128,6 +153,8 @@ private extension DeleteLinkBottomSheetView { editLinkTitleBottomSheetViewButtonAction?() case deleteButton: deleteLinkBottomSheetViewButtonAction?() + case changeClipButton: + changeClipBottomSheetViewButtonAction?() default: break } diff --git a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift index 5445fc4f..4f34e254 100644 --- a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift +++ b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift @@ -5,9 +5,11 @@ // Created by 김다예 on 12/30/23. // +import Combine import UIKit import SnapKit +import Then final class DetailClipViewController: UIViewController { @@ -16,20 +18,32 @@ final class DetailClipViewController: UIViewController { // MARK: - UI Properties private let viewModel = DetailClipViewModel() + private let changeClipViewModel = ChangeClipViewModel() private let detailClipSegmentedControlView = DetailClipSegmentedControlView() private let detailClipEmptyView = DetailClipEmptyView() private let detailClipListCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) - private let deleteLinkBottomSheetView = DeleteLinkBottomSheetView() - private lazy var editBottom = ToasterBottomSheetViewController(bottomType: .gray, - bottomTitle: "수정하기", - insertView: deleteLinkBottomSheetView) + private let linkOptionBottomSheetView = LinkOptionBottomSheetView() + private lazy var optionBottom = ToasterBottomSheetViewController(bottomType: .gray, + bottomTitle: "더보기", + insertView: linkOptionBottomSheetView) private let editLinkBottomSheetView = EditLinkBottomSheetView() private lazy var editLinkBottom = ToasterBottomSheetViewController(bottomType: .white, bottomTitle: "링크 제목 편집", insertView: editLinkBottomSheetView) + private let changeClipBottomSheetView = ChangeClipBottomSheetView() + private lazy var changeClipBottom = ToasterBottomSheetViewController(bottomType: .gray, + bottomTitle: "클립을 선택해 주세요", + insertView: changeClipBottomSheetView) + + private let changeClipSubject = PassthroughSubject() + private let selectedClipSubject = PassthroughSubject() + private let completeButtonSubject = PassthroughSubject() + + private var cancelBag = CancelBag() + // MARK: - Life Cycle override func viewDidLoad() { @@ -41,6 +55,7 @@ final class DetailClipViewController: UIViewController { setupRegisterCell() setupDelegate() setupViewModel() + bindViewModels() } override func viewWillAppear(_ animated: Bool) { @@ -57,6 +72,7 @@ extension DetailClipViewController { func setupCategory(id: Int, name: String) { viewModel.categoryId = id viewModel.categoryName = name + changeClipViewModel.setupCateogry(id) } } @@ -104,6 +120,7 @@ private extension DetailClipViewController { detailClipListCollectionView.dataSource = self detailClipSegmentedControlView.detailClipSegmentedDelegate = self viewModel.delegate = self + changeClipBottomSheetView.delegate = self } func setupViewModel() { @@ -135,6 +152,52 @@ private extension DetailClipViewController { navigationController.setupNavigationBar(forType: type) } } + + func bindViewModels() { + let input = ChangeClipViewModel.Input( + changeButtonTap: changeClipSubject.eraseToAnyPublisher(), + selectedClip: selectedClipSubject.eraseToAnyPublisher(), + completeButtonTap: completeButtonSubject.eraseToAnyPublisher() + ) + + let output = changeClipViewModel.transform(input, cancelBag: cancelBag) + + output.clipData + .receive(on: DispatchQueue.main) + .sink { [weak self] clipData in + self?.changeClipBottomSheetView.dataSourceHandler = { clipData } + self?.changeClipBottomSheetView.reloadChangeClipBottom() + } + .store(in: cancelBag) + + output.isCompleteButtonEnable + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + self?.changeClipBottomSheetView.updateCompleteButtonUI(result) + } + .store(in: cancelBag) + + output.changeCategoryResult + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self else { return } + if result == true { + + self.changeClipBottom.dismiss(animated: true) { + if self.viewModel.categoryId == 0 { + self.viewModel.getDetailAllCategoryAPI(filter: .all) + } else { + self.viewModel.getDetailCategoryAPI(categoryID: self.viewModel.categoryId, filter: .all) + } + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.showToastMessage(width: 152, status: .check, message: "링크 이동 완료") + } + } + } + .store(in: cancelBag) + } } // MARK: - CollectionView DataSource @@ -158,15 +221,26 @@ extension DetailClipViewController: UICollectionViewDataSource { cell.configureCell(forModel: viewModel.toastList, index: indexPath.item, isClipHidden: true) } - /// "수정하기" 클릭 시 - deleteLinkBottomSheetView.setupEditLinkTitleBottomSheetButtonAction { + // "수정하기" 클릭 시 + linkOptionBottomSheetView.setupEditLinkTitleBottomSheetButtonAction { self.dismiss(animated: true) { self.editLinkBottom.setupSheetPresentation(bottomHeight: 198) self.present(self.editLinkBottom, animated: true) } } - /// "삭제" 클릭 시 - deleteLinkBottomSheetView.setupDeleteLinkBottomSheetButtonAction { + + // "클립이동" 클릭 시 + linkOptionBottomSheetView.setupChangeClipBottomSheetButtonAction { + self.changeClipSubject.send() + + self.dismiss(animated: true) { + self.changeClipBottom.setupSheetPresentation(bottomHeight: self.changeClipViewModel.collectionViewHeight + 185) + self.present(self.changeClipBottom, animated: true) + } + } + + // "삭제" 클릭 시 + linkOptionBottomSheetView.setupDeleteLinkBottomSheetButtonAction { self.viewModel.deleteLinkAPI(toastId: self.viewModel.toastId) self.dismiss(animated: true) { [weak self] in self?.showToastMessage(width: 152, status: .check, message: StringLiterals.ToastMessage.completeDeleteLink) @@ -270,8 +344,9 @@ extension DetailClipViewController: DetailClipSegmentedDelegate { extension DetailClipViewController: DetailClipListCollectionViewCellDelegate { func modifiedButtonTapped(toastId: Int) { viewModel.toastId = toastId - editBottom.setupSheetPresentation(bottomHeight: 226) - present(editBottom, animated: true) + changeClipViewModel.setupToastId(toastId) + optionBottom.setupSheetPresentation(bottomHeight: 280) + present(optionBottom, animated: true) } } @@ -309,3 +384,13 @@ extension DetailClipViewController: PatchClipDelegate { } } } + +extension DetailClipViewController: ChangeClipBottomSheetViewDelegate { + func didSelectClip(selectClipId: Int) { + selectedClipSubject.send(selectClipId) + } + + func completButtonTap() { + completeButtonSubject.send() + } +} diff --git a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift new file mode 100644 index 00000000..ffb82bb0 --- /dev/null +++ b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift @@ -0,0 +1,169 @@ +// +// ChangeClipViewModel.swift +// TOASTER-iOS +// +// Created by ParkJunHyuk on 10/9/24. +// + +import Combine +import Foundation + +final class ChangeClipViewModel: ViewModelType { + + private(set) var currentToastId: Int = 0 + private(set) var currentCategoryId: Int = 0 + private(set) var botomHeigth: CGFloat = 0 + private(set) var collectionViewHeight: CGFloat = 0 + + struct Input { + let changeButtonTap: AnyPublisher + let selectedClip: AnyPublisher + let completeButtonTap: AnyPublisher + } + + struct Output { + let clipData: AnyPublisher<[SelectClipModel], Never> + let isCompleteButtonEnable: AnyPublisher + let changeCategoryResult: AnyPublisher + } + + func transform(_ input: Input, cancelBag: CancelBag) -> Output { + + /// 클립이동 버튼이 눌렸을때 동작 + let clipDataPublisher = input.changeButtonTap + .flatMap { [weak self] _ -> AnyPublisher<[SelectClipModel], Never> in + guard let self else { + return Just([]).eraseToAnyPublisher() + } + + return self.getAllCategoryAPI() + .map { [weak self] result -> [SelectClipModel] in + guard let self = self else { return [] } + let sortedResult = self.sortCurrentCategoryToTop(result) + self.collectionViewHeight = self.calculateCollectionViewHeight(numberOfItems: sortedResult.count) + return sortedResult + } + .catch { error -> AnyPublisher<[SelectClipModel], Never> in + print("Error: \(error)") + return Just([]).eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } .eraseToAnyPublisher() + + /// 이동할 클립을 선택 시 버튼의 UI 를 변경하는 동작 + let isCompleteButtonEnable = input.selectedClip + .map { _ in true } + .eraseToAnyPublisher() + + /// 완료 버튼이 눌렸을때 동작 + let changeCategoryResult = input.completeButtonTap + .combineLatest(input.selectedClip) { _, selectedClip in + return selectedClip + } + .flatMap { [weak self] selectedClip -> AnyPublisher in + guard let self else { + return Just(false).eraseToAnyPublisher() + } + + return self.patchChagneCategory(categoryId: selectedClip) + .catch { error -> AnyPublisher in + print("Error: \(error)") + return Just(false).eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + + return Output( + clipData: clipDataPublisher, + isCompleteButtonEnable: isCompleteButtonEnable, + changeCategoryResult: changeCategoryResult + ) + } + + func setupCateogry(_ id: Int) { + currentCategoryId = id + } + + func setupToastId(_ id: Int) { + currentToastId = id + } +} + +// MARK: - private Extensions + +private extension ChangeClipViewModel { + /// 현재 카테고리를 최상단에 위치하도록 정렬하는 메서드 + func sortCurrentCategoryToTop(_ clipDataList: [SelectClipModel]) -> [SelectClipModel] { + guard let currentCategoryIndex = clipDataList.firstIndex(where: { $0.id == currentCategoryId }) else { + return clipDataList + } + + var tempClipDataList = clipDataList + let currentCategoryData = tempClipDataList.remove(at: currentCategoryIndex) + tempClipDataList.insert(currentCategoryData, at: 0) + + calculateBottomSheetHeight(clipDataList.count) + + return tempClipDataList + } + + func calculateBottomSheetHeight(_ count: Int) { + botomHeigth = CGFloat(count * 54 + 184 + 3) + } + + func calculateCollectionViewHeight(numberOfItems: Int) -> CGFloat { + let cellHeight: CGFloat = 54 + let lineSpacing: CGFloat = 1 + + // 마지막 셀 다음에는 간격이 없으므로 (numberOfItems - 1) + let totalHeight = (cellHeight * CGFloat(numberOfItems)) + (lineSpacing * CGFloat(numberOfItems - 1)) + print("높이:", totalHeight) + return totalHeight + } +} + +// MARK: - API Extensions + +extension ChangeClipViewModel { + func getAllCategoryAPI() -> AnyPublisher<[SelectClipModel], Error> { + return Future<[SelectClipModel], Error> { promise in + NetworkService.shared.clipService.getAllCategory { result in + switch result { + case .success(let response): + let clipDataList = response?.data.categories.map { category in + SelectClipModel( + id: category.categoryId, + title: category.categoryTitle, + clipCount: category.toastNum + ) + } ?? [] + + promise(.success(clipDataList)) + case .unAuthorized, .networkFail, .notFound: + promise(.failure(NetworkResult.unAuthorized)) + default: + break + } + } + }.eraseToAnyPublisher() + } + + func patchChagneCategory(categoryId: Int) -> AnyPublisher { + let requestDTO = PatchChangeCategoryRequestDTO(toastId: currentToastId, categoryId: categoryId) + + return Future { promise in + NetworkService.shared.toastService.patchChangeCategory(requestBody: requestDTO) { result in + switch result { + case .success: + promise(.success(true)) + case .unAuthorized, .networkFail, .notFound, .serverErr: + promise(.failure(NetworkResult.unAuthorized)) + default: + break + } + } + + }.eraseToAnyPublisher() + } +} From 66e3524da87c76f4990267d5b472a3e18c392156 Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 24 Oct 2024 01:38:12 +0900 Subject: [PATCH 67/98] =?UTF-8?q?[Fix]=20#217=20-=2015=20=EC=9E=90=20?= =?UTF-8?q?=EC=B4=88=EA=B3=BC=20=EB=A7=81=ED=81=AC=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/EditLinkBottomSheetView.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/EditLinkBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/EditLinkBottomSheetView.swift index 49b4b644..4f74513e 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/EditLinkBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/EditLinkBottomSheetView.swift @@ -235,25 +235,10 @@ private extension EditLinkBottomSheetView { // MARK: - UITextField Delegate extension EditLinkBottomSheetView: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - let newText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) ?? string - let currentText = textField.text ?? "" - let maxLength = 16 - - // 길이가 16에서 15로 돌아갈 때 - if currentText.count == maxLength && newText.count == 15 { - editLinkBottomSheetViewDelegate?.minusHeightBottom() - } - return (newText.count <= maxLength) || (newText.count < currentText.count) - } - func textFieldDidChangeSelection(_ textField: UITextField) { let currentText = textField.text ?? "" if currentText.isEmpty { changeTextField(addButton: false, border: false, error: false, clearButton: false) - } else if currentText.count > 15 { - changeTextField(addButton: false, border: true, error: true, clearButton: true) - setupMessage(message: "링크 제목은 최대 15자까지 입력 가능해요") } else { changeTextField(addButton: true, border: false, error: false, clearButton: true) } From 7360253a66b0da5c7971f50d072c87fa2e1bc4f6 Mon Sep 17 00:00:00 2001 From: Genesis Date: Thu, 24 Oct 2024 01:38:31 +0900 Subject: [PATCH 68/98] =?UTF-8?q?[Fix]=20#217-=20bottomSheet=20=EB=86=92?= =?UTF-8?q?=EC=9D=B4=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=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 --- TOASTER-iOS.xcodeproj/project.pbxproj | 18 ++++++++++++++---- .../ToasterBottomSheetViewController.swift | 4 ++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index 867aefe5..67b54064 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ 39BE4BB62B4ABA97002B471D /* DetailClipListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE4BB52B4ABA97002B471D /* DetailClipListCollectionViewCell.swift */; }; 39BE4BBE2B4ABB7C002B471D /* DetailClipSegmentedControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE4BBD2B4ABB7C002B471D /* DetailClipSegmentedControlView.swift */; }; 39BE4BC02B4ABB9E002B471D /* DetailClipEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE4BBF2B4ABB9E002B471D /* DetailClipEmptyView.swift */; }; - 39BE4BC22B4ABBB0002B471D /* DeleteLinkBottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE4BC12B4ABBB0002B471D /* DeleteLinkBottomSheetView.swift */; }; + 39BE4BC22B4ABBB0002B471D /* LinkOptionBottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE4BC12B4ABBB0002B471D /* LinkOptionBottomSheetView.swift */; }; 39C3926B2B491F47005B2B0F /* NSObject+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C3926A2B491F47005B2B0F /* NSObject+.swift */; }; 39E794F92B479C8600F16A38 /* ClipEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E794F82B479C8600F16A38 /* ClipEmptyView.swift */; }; 39EF95FF2B501BD600F301FC /* EditClipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EF95FE2B501BD600F301FC /* EditClipViewController.swift */; }; @@ -169,6 +169,9 @@ 3FEA674B2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */; }; 3FEA674D2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674C2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift */; }; 3FEA674E2CB51E6D00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */; }; + 3FEA67502CB6522F00675805 /* ChangeClipBottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674F2CB6522F00675805 /* ChangeClipBottomSheetView.swift */; }; + 3FEA67522CB663B100675805 /* ChangeClipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA67512CB663B100675805 /* ChangeClipViewModel.swift */; }; + 3FEA67532CB677A400675805 /* PatchChangeCategoryResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEA674C2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift */; }; 3FF02B302CAFE6600074332E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */; }; 3FF2BF092BA17492001D7DC1 /* ToasterShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3FF2BEFF2BA17492001D7DC1 /* ToasterShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3FF2BF0E2BA188AE001D7DC1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B6AE6572B3FF103000E2366 /* Assets.xcassets */; }; @@ -389,7 +392,7 @@ 39BE4BB52B4ABA97002B471D /* DetailClipListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipListCollectionViewCell.swift; sourceTree = ""; }; 39BE4BBD2B4ABB7C002B471D /* DetailClipSegmentedControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipSegmentedControlView.swift; sourceTree = ""; }; 39BE4BBF2B4ABB9E002B471D /* DetailClipEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailClipEmptyView.swift; sourceTree = ""; }; - 39BE4BC12B4ABBB0002B471D /* DeleteLinkBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteLinkBottomSheetView.swift; sourceTree = ""; }; + 39BE4BC12B4ABBB0002B471D /* LinkOptionBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkOptionBottomSheetView.swift; sourceTree = ""; }; 39C3926A2B491F47005B2B0F /* NSObject+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+.swift"; sourceTree = ""; }; 39E794F82B479C8600F16A38 /* ClipEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipEmptyView.swift; sourceTree = ""; }; 39EF95FE2B501BD600F301FC /* EditClipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClipViewController.swift; sourceTree = ""; }; @@ -422,6 +425,8 @@ 3FE828342B54E58E00F10732 /* APIInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIInterceptor.swift; sourceTree = ""; }; 3FEA674A2CB51BBC00675805 /* PatchChangeCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchChangeCategoryRequestDTO.swift; sourceTree = ""; }; 3FEA674C2CB51BFC00675805 /* PatchChangeCategoryResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchChangeCategoryResponseDTO.swift; sourceTree = ""; }; + 3FEA674F2CB6522F00675805 /* ChangeClipBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeClipBottomSheetView.swift; sourceTree = ""; }; + 3FEA67512CB663B100675805 /* ChangeClipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeClipViewModel.swift; sourceTree = ""; }; 3FF2BEFF2BA17492001D7DC1 /* ToasterShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ToasterShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 3FF2BF062BA17492001D7DC1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6B0E85D82B564913001BC15F /* RemindTimerAddViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindTimerAddViewModel.swift; sourceTree = ""; }; @@ -791,6 +796,7 @@ children = ( 39A843C92B74512B007A4D75 /* DetailClipViewModel.swift */, 39A843CD2B745B3A007A4D75 /* DetailClipPropertyType.swift */, + 3FEA67512CB663B100675805 /* ChangeClipViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -800,8 +806,9 @@ children = ( 39BE4BBD2B4ABB7C002B471D /* DetailClipSegmentedControlView.swift */, 39BE4BBF2B4ABB9E002B471D /* DetailClipEmptyView.swift */, - 39BE4BC12B4ABBB0002B471D /* DeleteLinkBottomSheetView.swift */, + 39BE4BC12B4ABBB0002B471D /* LinkOptionBottomSheetView.swift */, 8388E98D2BC8FC6700858C5C /* EditLinkBottomSheetView.swift */, + 3FEA674F2CB6522F00675805 /* ChangeClipBottomSheetView.swift */, ); path = Component; sourceTree = ""; @@ -1954,6 +1961,7 @@ 3F3ED29D2BA1A475004E79F0 /* PostCreateTimerRequestDTO.swift in Sources */, 3FEA674E2CB51E6D00675805 /* PatchChangeCategoryRequestDTO.swift in Sources */, 3F1F26262BAAC231004F75CE /* ShareViewController.swift in Sources */, + 3FEA67532CB677A400675805 /* PatchChangeCategoryResponseDTO.swift in Sources */, 3F1F26222BAAB395004F75CE /* ToasterToastMessageView.swift in Sources */, 3F3ED2992BA1A46E004E79F0 /* PatchOpenLinkResponseDTO.swift in Sources */, 3F3ED2872BA1A400004E79F0 /* PostAddCategoryRequestDTO.swift in Sources */, @@ -2046,7 +2054,7 @@ 3909A0702B6239F8005A4546 /* ClipPriorityEditModel.swift in Sources */, 6BE6DA962B547037008B06FA /* PatchEditTimerRequestDTO.swift in Sources */, 39B54E732B53C50300538DAE /* SettingViewController.swift in Sources */, - 39BE4BC22B4ABBB0002B471D /* DeleteLinkBottomSheetView.swift in Sources */, + 39BE4BC22B4ABBB0002B471D /* LinkOptionBottomSheetView.swift in Sources */, 6BE6DA292B505433008B06FA /* AuthTargetType.swift in Sources */, 6BC4936C2B48633700544249 /* ToasterNavigationType.swift in Sources */, 6B6AE6992B3FF5C1000E2366 /* SearchViewController.swift in Sources */, @@ -2091,6 +2099,7 @@ 8309F5882B8DCEAC00A1420A /* SelectClipViewModel.swift in Sources */, 39049C8F2B43F70400C9196E /* ToasterBottomSheetViewController.swift in Sources */, 6BE6DAB12B547BE1008B06FA /* GetMainPageSearchResponseDTO.swift in Sources */, + 3FEA67502CB6522F00675805 /* ChangeClipBottomSheetView.swift in Sources */, 8315CD862B517DBF0061F377 /* AddLinkView.swift in Sources */, 8315CD912B5521F70061F377 /* SelectClipModel.swift in Sources */, 3FA8654F2BBD799600A9DB8F /* PostTokenHealthResponseDTO.swift in Sources */, @@ -2122,6 +2131,7 @@ 3F2FA1752B45C0AF00EDBF95 /* Config.swift in Sources */, 3F82C3212CADA19300492EEE /* Publisher+UIButton.swift in Sources */, 6B6AE6AC2B3FF6F7000E2366 /* UIColor+.swift in Sources */, + 3FEA67522CB663B100675805 /* ChangeClipViewModel.swift in Sources */, 6BC493682B45D7B100544249 /* ToasterNavigationController.swift in Sources */, 6BE6DA492B50ADC2008B06FA /* NetworkService.swift in Sources */, 6B6AE69C2B3FF5CC000E2366 /* HomeViewController.swift in Sources */, diff --git a/TOASTER-iOS/Global/Components/ToasterBottomSheet/ToasterBottomSheetViewController.swift b/TOASTER-iOS/Global/Components/ToasterBottomSheet/ToasterBottomSheetViewController.swift index ca1a09ec..1e5f1682 100644 --- a/TOASTER-iOS/Global/Components/ToasterBottomSheet/ToasterBottomSheetViewController.swift +++ b/TOASTER-iOS/Global/Components/ToasterBottomSheet/ToasterBottomSheetViewController.swift @@ -44,7 +44,7 @@ final class ToasterBottomSheetViewController: UIViewController { extension ToasterBottomSheetViewController { func setupSheetPresentation(bottomHeight: CGFloat) { if let sheet = self.sheetPresentationController { - sheet.detents = [.custom(resolver: { _ in bottomHeight - 40 })] + sheet.detents = [.custom(resolver: { _ in bottomHeight - (self.view.hasNotch ? 34 : 0)})] sheet.preferredCornerRadius = 20 } } @@ -52,7 +52,7 @@ extension ToasterBottomSheetViewController { func setupSheetHeightChanges(bottomHeight: CGFloat) { if let sheet = self.sheetPresentationController { sheet.animateChanges { - sheet.detents = [.custom(resolver: { _ in bottomHeight - 40 })] + sheet.detents = [.custom(resolver: { _ in bottomHeight - (self.view.hasNotch ? 34 : 0)})] } } } From 0ccb77da62dc4957652bec7184f5113dd7b66457 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Sat, 26 Oct 2024 16:55:04 +0900 Subject: [PATCH 69/98] =?UTF-8?q?[Chore]=20#215=20-=20EmptyView=20text=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/View/Cell/UserClipEmptyCollectionViewCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift b/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift index d218cdf7..f28dad57 100644 --- a/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift +++ b/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift @@ -55,7 +55,7 @@ private extension UserClipEmptyCollectionViewCell { addClipLabel.do { $0.font = .suitBold(size: 14) $0.textColor = .gray200 - $0.text = "클립 추가" + $0.text = "첫번째 링크를 저장해보세요" } } From 8d7078538f03c7366f4237a21b2c02cf9ec4d8a9 Mon Sep 17 00:00:00 2001 From: Genesis Date: Mon, 28 Oct 2024 23:53:02 +0900 Subject: [PATCH 70/98] =?UTF-8?q?[Fix]=20#217=20-=20=EC=82=BC=ED=95=AD?= =?UTF-8?q?=EC=97=B0=EC=82=B0=EC=9E=90=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?Filter=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=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 --- .../View/Component/ChangeClipBottomSheetView.swift | 10 +++------- .../DetailClip/View/DetailClipViewController.swift | 9 ++++++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift index 1e7f0e78..7efb9854 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift @@ -46,13 +46,9 @@ final class ChangeClipBottomSheetView: UIView { /// 버튼 활성화에 따른 UI 변경 func updateCompleteButtonUI(_ isEnable: Bool) { - if isEnable == true { - completeBottomButton.isUserInteractionEnabled = true - completeBottomButton.backgroundColor = .black850 - } else { - completeBottomButton.isUserInteractionEnabled = false - completeBottomButton.backgroundColor = .gray200 - } + + completeBottomButton.isUserInteractionEnabled = isEnable ? true : false + completeBottomButton.backgroundColor = isEnable ? .black850 : .gray200 } } diff --git a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift index 4f34e254..bd692856 100644 --- a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift +++ b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift @@ -182,17 +182,20 @@ private extension DetailClipViewController { .sink { [weak self] result in guard let self else { return } if result == true { + let categoryFilter = DetailCategoryFilter.allCases[viewModel.getViewModelProperty(dataType: .segmentIndex) as? Int ?? 0] self.changeClipBottom.dismiss(animated: true) { if self.viewModel.categoryId == 0 { - self.viewModel.getDetailAllCategoryAPI(filter: .all) + self.viewModel.getDetailAllCategoryAPI(filter: categoryFilter) } else { - self.viewModel.getDetailCategoryAPI(categoryID: self.viewModel.categoryId, filter: .all) + self.viewModel.getDetailCategoryAPI(categoryID: self.viewModel.categoryId, filter: categoryFilter) } } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.showToastMessage(width: 152, status: .check, message: "링크 이동 완료") + self.showToastMessage(width: 152, + status: .check, + message: "링크 이동 완료") } } } From bed97a27f86bb0a15b30b4bec44328e09b517759 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 29 Oct 2024 18:06:01 +0900 Subject: [PATCH 71/98] =?UTF-8?q?[Feat]=20#220=20-=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EB=B7=B0=20=EC=B5=9C=EC=B4=88=20=EC=A7=84=EC=9E=85=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=EC=9A=A9=20UserDefaults=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TOASTER-iOS.xcodeproj/project.pbxproj | 6 ++++ .../ToasterTipView/TipUserDefaults.swift | 14 +++++++++ .../LinkWebViewController.swift | 29 ++++++++++--------- 3 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 TOASTER-iOS/Global/Components/ToasterTipView/TipUserDefaults.swift diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index e854ca77..ad157301 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ 3913B0B02BCECFC80031A3EB /* UpdateAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */; }; 391908422B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */; }; 391908442B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */; }; + 392461802CD0D1FB00C0CBC4 /* TipUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3924617F2CD0D1FB00C0CBC4 /* TipUserDefaults.swift */; }; + 392461812CD0D1FB00C0CBC4 /* TipUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3924617F2CD0D1FB00C0CBC4 /* TipUserDefaults.swift */; }; 396D7ECB2C855F5F0034A14E /* LinkWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */; }; 396D7ECD2C880F1F0034A14E /* LinkWebToolBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */; }; 396DCDF42CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DCDF32CA19EC600FEF7C8 /* PatchPopupHiddenResponseDTO.swift */; }; @@ -358,6 +360,7 @@ 3913B0AF2BCECFC80031A3EB /* UpdateAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateAlertType.swift; sourceTree = ""; }; 391908412B56CFE4006F978A /* PatchEditPriorityCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditPriorityCategoryRequestDTO.swift; sourceTree = ""; }; 391908432B56D027006F978A /* PatchEditNameCategoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditNameCategoryRequestDTO.swift; sourceTree = ""; }; + 3924617F2CD0D1FB00C0CBC4 /* TipUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipUserDefaults.swift; sourceTree = ""; }; 396D7EC72C855F180034A14E /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; 396D7ECA2C855F5F0034A14E /* LinkWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebViewModel.swift; sourceTree = ""; }; 396D7ECC2C880F1F0034A14E /* LinkWebToolBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkWebToolBarView.swift; sourceTree = ""; }; @@ -773,6 +776,7 @@ children = ( 39A232DF2CB8F29A00ACC803 /* ToasterTipView.swift */, 39A232DC2CB8F28000ACC803 /* TipPathView.swift */, + 3924617F2CD0D1FB00C0CBC4 /* TipUserDefaults.swift */, ); path = ToasterTipView; sourceTree = ""; @@ -1995,6 +1999,7 @@ 3F3ED2A42BA1A47E004E79F0 /* GetRecommendSiteResponseDTO.swift in Sources */, 3F3ED2A52BA1A47E004E79F0 /* SearchAPIService.swift in Sources */, 39AE73CD2CB4EBC300F89793 /* ToasterLoadingView.swift in Sources */, + 392461812CD0D1FB00C0CBC4 /* TipUserDefaults.swift in Sources */, 3F3ED2962BA1A46B004E79F0 /* PostSaveLinkRequestDTO.swift in Sources */, 3F3ED2882BA1A400004E79F0 /* PatchEditPriorityCategoryRequestDTO.swift in Sources */, 3FE00F082BC5076200CC821E /* PostTokenHealthResponseDTO.swift in Sources */, @@ -2038,6 +2043,7 @@ 3F6CD49D2B86229A00DEC113 /* CustomPageIndicatorView.swift in Sources */, 6BE6DAB32B547BEF008B06FA /* GetRecommendSiteResponseDTO.swift in Sources */, 6B6AE6532B3FF101000E2366 /* ViewController.swift in Sources */, + 392461802CD0D1FB00C0CBC4 /* TipUserDefaults.swift in Sources */, 6B6AE6902B3FF59C000E2366 /* DetailClipViewController.swift in Sources */, 6BE6DA202B504433008B06FA /* BaseTargetType.swift in Sources */, 6B6AE6A62B3FF6BD000E2366 /* UIView+.swift in Sources */, diff --git a/TOASTER-iOS/Global/Components/ToasterTipView/TipUserDefaults.swift b/TOASTER-iOS/Global/Components/ToasterTipView/TipUserDefaults.swift new file mode 100644 index 00000000..f641f4cc --- /dev/null +++ b/TOASTER-iOS/Global/Components/ToasterTipView/TipUserDefaults.swift @@ -0,0 +1,14 @@ +// +// TipUserDefaults.swift +// TOASTER-iOS +// +// Created by 민 on 10/29/24. +// + +import Foundation + +enum TipUserDefaults { + static let isShowHomeViewToolTip = "homeViewToolTip" + static let isShowDetailClipViewToolTip = "detailClipViewToolTip" + static let isShowLinkWebViewToolTip = "linkWebViewToolTip" +} diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 67e205d3..64429b0d 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -51,6 +51,7 @@ final class LinkWebViewController: UIViewController { setupLayout() setupNavigationBarAction() setupToolBarAction() + setupToolTip() } override func viewWillAppear(_ animated: Bool) { @@ -65,19 +66,6 @@ final class LinkWebViewController: UIViewController { showNavigationBar() } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - DispatchQueue.main.asyncAfter(deadline: .now()+3) { - self.view.addSubview(self.secondToolTip) - self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 3) { - self.view.addSubview(self.firstToolTip) - self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 3) - } - } - - } - deinit { progressObservation?.invalidate() } @@ -210,6 +198,21 @@ private extension LinkWebViewController { if let url = self.webView.url { UIApplication.shared.open(url) } } } + + func setupToolTip() { + if UserDefaults.standard.value(forKey: TipUserDefaults.isShowLinkWebViewToolTip) == nil { + UserDefaults.standard.set(true, forKey: TipUserDefaults.isShowLinkWebViewToolTip) + + DispatchQueue.main.asyncAfter(deadline: .now()+1) { [weak self] in + guard let self else { return } + self.view.addSubview(self.secondToolTip) + self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) { + self.view.addSubview(self.firstToolTip) + self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 4) + } + } + } + } } // MARK: - WKNavigationDelegate Extensions From 1e4d3e1bf70eb64d80597943909b99c2bdffce32 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Fri, 1 Nov 2024 22:15:55 +0900 Subject: [PATCH 72/98] =?UTF-8?q?[Feat]=20#220=20-=20HomeVC=20=EC=84=9C?= =?UTF-8?q?=EC=B9=98=20=ED=83=AD=EB=B0=94=20=ED=88=B4=ED=8C=81=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/HomeViewController.swift | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index d9edf9b4..147345dc 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -21,6 +21,20 @@ final class HomeViewController: UIViewController { bottomTitle: "클립 추가", insertView: addClipBottomSheetView) +// private lazy var firstToolTip = ToasterTipView( +// title: "마지막으로 저장한 링크를\n확인하러 가보세요!", +// type: .left, +// sourceItem: navigationView.addressLabel +// ) + + private lazy var secondToolTip: ToasterTipView? = { + guard let tabBarItems = tabBarController?.tabBar.items else { return nil } + let firstTabItem = tabBarItems[3] + let sourceItemFrame = firstTabItem.value(forKey: "view") as? UIView ?? UIView() + + return ToasterTipView(title: "검색이 더욱 편리해졌어요", type: .top, sourceItem: sourceItemFrame) + }() + // MARK: - Life Cycle override func viewDidLoad() { @@ -42,6 +56,11 @@ final class HomeViewController: UIViewController { viewModel.fetchRecommendSiteData() viewModel.getPopupInfoAPI() } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + setupToolTip() + } } // MARK: - UICollectionViewDelegate @@ -265,6 +284,25 @@ private extension HomeViewController { popupAction: showPopupAction) } + func setupToolTip() { + guard let secondToolTip else { return } + + view.addSubview(secondToolTip) + secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) +// if UserDefaults.standard.value(forKey: TipUserDefaults.isShowHomeViewToolTip) == nil { +// UserDefaults.standard.set(true, forKey: TipUserDefaults.isShowHomeViewToolTip) +// +// DispatchQueue.main.asyncAfter(deadline: .now()+1) { [weak self] in +// guard let self else { return } +// self.view.addSubview(self.secondToolTip) +// self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) { +// self.view.addSubview(self.firstToolTip) +// self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 4) +// } +// } +// } + } + func reloadCollectionView(isHidden: Bool) { homeView.collectionView.reloadData() } From 778e3d1217905cf2a68b1a261ddbb6d36893fb5b Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Sat, 2 Nov 2024 01:20:27 +0900 Subject: [PATCH 73/98] =?UTF-8?q?[Chore]=20#215=20-=20EmptyClipCell=20Layo?= =?UTF-8?q?ut=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Cell/UserClipEmptyCollectionViewCell.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift b/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift index f28dad57..bb7385f4 100644 --- a/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift +++ b/TOASTER-iOS/Present/Home/View/Cell/UserClipEmptyCollectionViewCell.swift @@ -66,13 +66,13 @@ private extension UserClipEmptyCollectionViewCell { func setupLayout() { addClipImage.snp.makeConstraints { $0.centerX.equalToSuperview() - $0.top.equalToSuperview().inset(34) + $0.top.equalToSuperview().inset(17) $0.size.equalTo(42) } addClipLabel.snp.makeConstraints { $0.centerX.equalToSuperview() - $0.top.equalTo(addClipImage.snp.bottom).offset(1) + $0.top.equalTo(addClipImage.snp.bottom).offset(5) } } From be9ba0c32bd2f7edf13189010d1107c0e1211b4d Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Sat, 2 Nov 2024 15:50:14 +0900 Subject: [PATCH 74/98] =?UTF-8?q?[Feat]=20#215=20-=20RecentLink=20EmptyCel?= =?UTF-8?q?l=20=EC=B6=94=EA=B0=80,=20Web=20=EC=9D=B4=EB=8F=99=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/HomeViewController.swift | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index 9ffce598..f621c272 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -14,6 +14,7 @@ final class HomeViewController: UIViewController { // MARK: - UI Properties private let viewModel = HomeViewModel() + private let clipViewModel = DetailClipViewModel() private let homeView = HomeView() private let addClipBottomSheetView = AddClipBottomSheetView() @@ -51,12 +52,13 @@ extension HomeViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { switch indexPath.section { case 1: - let data = viewModel.mainInfoList.mainCategoryListDto + let data = viewModel.recentLink if indexPath.item < data.count { - let nextVC = DetailClipViewController() - nextVC.setupCategory(id: data[indexPath.item].categoryId, - name: data[indexPath.item].categroyTitle) + let nextVC = LinkWebViewController() nextVC.hidesBottomBarWhenPushed = true + nextVC.setupDataBind(linkURL: viewModel.recentLink[indexPath.item].linkUrl, + isRead: viewModel.recentLink[indexPath.item].isRead, + id: viewModel.recentLink[indexPath.item].toastId) self.navigationController?.pushViewController(nextVC, animated: true) } else { addClipCellTapped() @@ -93,7 +95,7 @@ extension HomeViewController: UICollectionViewDataSource { return 1 case 1: let count = viewModel.recentLink.count - return min(count + 1, 3) + return count == 0 ? 1 : min(count, 3) case 2: return viewModel.weeklyLinkList.count case 3: @@ -115,19 +117,21 @@ extension HomeViewController: UICollectionViewDataSource { return cell case 1: let lastIndex = viewModel.recentLink.count - if indexPath.item == lastIndex && lastIndex < 3 { + if lastIndex == 0 { guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: DetailClipListCollectionViewCell.className, + withReuseIdentifier: UserClipEmptyCollectionViewCell.className, for: indexPath - ) as? DetailClipListCollectionViewCell else { return UICollectionViewCell() } + ) as? UserClipEmptyCollectionViewCell else { return UICollectionViewCell() } return cell } else { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: DetailClipListCollectionViewCell.className, for: indexPath ) as? DetailClipListCollectionViewCell else { return UICollectionViewCell() } - let model = viewModel.recentLink - cell.configureCell(forModel: model[indexPath.item], isClipHidden: false) + if indexPath.item < lastIndex { + let model = viewModel.recentLink + cell.configureCell(forModel: model[indexPath.item], isClipHidden: false) + } return cell } case 2: @@ -224,12 +228,12 @@ private extension HomeViewController { homeCollectionView.do { $0.register(MainCollectionViewCell.self, forCellWithReuseIdentifier: MainCollectionViewCell.className) - $0.register(UserClipCollectionViewCell.self, - forCellWithReuseIdentifier: UserClipCollectionViewCell.className) $0.register(WeeklyLinkCollectionViewCell.self, forCellWithReuseIdentifier: WeeklyLinkCollectionViewCell.className) $0.register(WeeklyRecommendCollectionViewCell.self, forCellWithReuseIdentifier: WeeklyRecommendCollectionViewCell.className) + $0.register(UserClipEmptyCollectionViewCell.self, + forCellWithReuseIdentifier: UserClipEmptyCollectionViewCell.className) $0.register(DetailClipListCollectionViewCell.self, forCellWithReuseIdentifier: DetailClipListCollectionViewCell.className) @@ -362,7 +366,7 @@ extension HomeViewController: AddClipBottomSheetViewDelegate { extension HomeViewController: UserClipCollectionViewCellDelegate { func addClipCellTapped() { - addClipBottom.setupSheetPresentation(bottomHeight: 198) - self.present(addClipBottom, animated: true) + let nextVC = AddLinkViewController() + self.navigationController?.pushViewController(nextVC, animated: true) } } From 0f97642344099e78922687361955ea0998afb585 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Sat, 2 Nov 2024 16:47:03 +0900 Subject: [PATCH 75/98] =?UTF-8?q?[Feat]=20#215=20-=20headerview=20icon=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20VC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Cell/HomeHeaderCollectionView.swift | 18 ++++++++++++++++-- .../Present/Home/View/HomeViewController.swift | 9 +++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift b/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift index 5602adfe..a9a5a55d 100644 --- a/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift +++ b/TOASTER-iOS/Present/Home/View/Cell/HomeHeaderCollectionView.swift @@ -14,6 +14,7 @@ final class HomeHeaderCollectionView: UICollectionReusableView { // MARK: - Properties private let titleLabel = UILabel() + let arrowButton = UIButton() // MARK: - Life Cycle @@ -21,6 +22,7 @@ final class HomeHeaderCollectionView: UICollectionReusableView { super.init(frame: frame) self.backgroundColor = .clear + setView() } @@ -41,21 +43,32 @@ final class HomeHeaderCollectionView: UICollectionReusableView { $0.textColor = .black900 $0.font = .suitMedium(size: 18) } + + arrowButton.do { + $0.setImage(.icArrow20, for: .normal) + $0.isUserInteractionEnabled = true + $0.isHidden = false + } } // MARK: - set up Hierarchy private func setupHierarchy() { - addSubview(titleLabel) + addSubviews(titleLabel, arrowButton) } // MARK: - set up Layout private func setupLayout() { titleLabel.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(10) + $0.leading.equalToSuperview().inset(10) $0.bottom.equalToSuperview().inset(5) } + + arrowButton.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.top).inset(1) + $0.trailing.equalToSuperview().inset(10) + } } } @@ -68,6 +81,7 @@ extension HomeHeaderCollectionView { } else { titleLabel.text = forTitle titleLabel.asFont(targetString: forTitle, font: .suitBold(size: 18)) + arrowButton.isHidden = true } } } diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index f621c272..5e10027f 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -27,6 +27,7 @@ final class HomeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() homeView.backgroundColor = .toasterBackground + setupHierarchy() setupLayout() createCollectionView() @@ -171,6 +172,7 @@ extension HomeViewController: UICollectionViewDataSource { let nickName = viewModel.mainInfoList.nickname header.configureHeader(forTitle: nickName, num: indexPath.section) + header.arrowButton.addTarget(self, action: #selector(arrowButtonTapped), for: .touchUpInside) case 2: header.configureHeader(forTitle: "이주의 링크", num: indexPath.section) @@ -340,6 +342,13 @@ private extension HomeViewController { settingVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(settingVC, animated: true) } + + @objc + func arrowButtonTapped() { + print("ARROW BUTTON TAPPED") + let clipViewController = ClipViewController() + navigationController?.pushViewController(clipViewController, animated: true) + } } // MARK: - AddClipBottomSheetViewDelegate From e45b51167c30440c49d4b85493276f906e4065e5 Mon Sep 17 00:00:00 2001 From: Genesis Date: Sun, 3 Nov 2024 15:49:28 +0900 Subject: [PATCH 76/98] =?UTF-8?q?[Fix]=20#217=20-=20=EC=85=80=20UI=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A1=9C?= =?UTF-8?q?=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 --- .../RemindSelectClipCollectionViewCell.swift | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift b/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift index f0d7ff24..e93521aa 100644 --- a/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift +++ b/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift @@ -83,26 +83,20 @@ extension RemindSelectClipCollectionViewCell { clipImageView.image = isSelected == true ? icon.withTintColor(.toasterPrimary) : icon self.isRounded = isRounded } - - func configureCurrentClipCell(forModel: SelectClipModel, icon: UIImage) { - currentCategoryTitle = forModel.title - clipTitleLabel.text = forModel.title - clipCountLabel.text = "\(forModel.clipCount)개" - clipImageView.image = icon - - clipTitleLabel.textColor = .gray200 - clipCountLabel.textColor = .gray200 - self.isRounded = false - } - - func configureChnageClipCell(forModel: SelectClipModel, icon: UIImage) { + /// 이동 할 카테고리 Cell 을 초기화 시키는 메서드 + func configureChangeClipCell(forModel: SelectClipModel, canSelect: Bool, icon: UIImage) { + + if canSelect == false { + currentCategoryTitle = forModel.title + } + clipTitleLabel.text = forModel.title clipCountLabel.text = "\(forModel.clipCount)개" - clipImageView.image = icon + clipImageView.image = icon.withTintColor(canSelect ? .toasterBlack : .gray200) - clipTitleLabel.textColor = .black850 - clipCountLabel.textColor = .gray600 + clipTitleLabel.textColor = canSelect ? .black850 : .gray200 + clipCountLabel.textColor = canSelect ? .gray600 : .gray200 self.isRounded = false } From 8b50d14ebd462a9215312ac73a98c77639d85f35 Mon Sep 17 00:00:00 2001 From: Genesis Date: Sun, 3 Nov 2024 15:53:24 +0900 Subject: [PATCH 77/98] =?UTF-8?q?[Fix]=20#217=20-=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailClip/ViewModel/ChangeClipViewModel.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift index ffb82bb0..e34edca1 100644 --- a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift +++ b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift @@ -51,9 +51,10 @@ final class ChangeClipViewModel: ViewModelType { } .eraseToAnyPublisher() /// 이동할 클립을 선택 시 버튼의 UI 를 변경하는 동작 - let isCompleteButtonEnable = input.selectedClip - .map { _ in true } - .eraseToAnyPublisher() + let isCompleteButtonEnable = Publishers.Merge( + input.changeButtonTap.map { false }, // bottomSheet 열릴 때 false + input.selectedClip.map { _ in true } // 클립 선택 시 true + ).eraseToAnyPublisher() /// 완료 버튼이 눌렸을때 동작 let changeCategoryResult = input.completeButtonTap @@ -81,7 +82,7 @@ final class ChangeClipViewModel: ViewModelType { ) } - func setupCateogry(_ id: Int) { + func setupCategory(_ id: Int) { currentCategoryId = id } From 978f65a8b4162483e3d1bdabb53eadef3669954c Mon Sep 17 00:00:00 2001 From: Genesis Date: Sun, 3 Nov 2024 15:53:54 +0900 Subject: [PATCH 78/98] =?UTF-8?q?[Fix]=20#217=20-=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=ED=81=B4=EB=A6=BD=EC=97=90=EC=84=9C=20=ED=81=B4=EB=A6=BD?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EC=98=B5=EC=85=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Component/ChangeClipBottomSheetView.swift | 10 +--- .../Component/LinkOptionBottomSheetView.swift | 52 +++++++++++++++++-- .../View/DetailClipViewController.swift | 6 +-- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift index 7efb9854..2f8e80af 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift @@ -46,7 +46,6 @@ final class ChangeClipBottomSheetView: UIView { /// 버튼 활성화에 따른 UI 변경 func updateCompleteButtonUI(_ isEnable: Bool) { - completeBottomButton.isUserInteractionEnabled = isEnable ? true : false completeBottomButton.backgroundColor = isEnable ? .black850 : .gray200 } @@ -61,7 +60,6 @@ private extension ChangeClipBottomSheetView { clipSelectCollectionView.do { $0.backgroundColor = .gray50 $0.makeRounded(radius: 12) - $0.clipsToBounds = true $0.isScrollEnabled = true $0.showsVerticalScrollIndicator = false } @@ -121,12 +119,8 @@ extension ChangeClipBottomSheetView: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RemindSelectClipCollectionViewCell.className, for: indexPath) as? RemindSelectClipCollectionViewCell, let clipData = dataSourceHandler?() else { return UICollectionViewCell() } - if indexPath.row == 0 { - cell.configureCurrentClipCell(forModel: clipData[indexPath.item], icon: .icClip24) - } else { - cell.configureChnageClipCell(forModel: clipData[indexPath.item], icon: .icClip24Black) - } - + cell.configureChangeClipCell(forModel: clipData[indexPath.item], canSelect: indexPath.row == 0 ? false : true, icon: .icClip24) + return cell } } diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift index c6439d4f..b2dde550 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift @@ -10,10 +10,21 @@ import UIKit import SnapKit import Then +enum ClipType { + case allClip + case anthoerClip + + init(categoryId: Int) { + self = categoryId == 0 ? .allClip : .anthoerClip + } +} + final class LinkOptionBottomSheetView: UIView { // MARK: - Properties + private let currentClipType: ClipType + private var deleteLinkBottomSheetViewButtonAction: (() -> Void)? private var editLinkTitleBottomSheetViewButtonAction: (() -> Void)? private var confirmBottomSheetViewButtonAction: (() -> Void)? @@ -30,7 +41,8 @@ final class LinkOptionBottomSheetView: UIView { // MARK: - Life Cycles - override init(frame: CGRect) { + init(currentClipType: ClipType, frame: CGRect = .zero) { + self.currentClipType = currentClipType super.init(frame: frame) setupStyle() @@ -107,13 +119,47 @@ private extension LinkOptionBottomSheetView { } func setupHierarchy() { - addSubviews(editButton, changeClipButton, deleteButton) + addSubviews(editButton, deleteButton) editButton.addSubview(editButtonLabel) - changeClipButton.addSubview(changeClipButtonLabel) deleteButton.addSubview(deleteButtonLabel) + + if case .anthoerClip = currentClipType { + addSubview(changeClipButton) + changeClipButton.addSubview(changeClipButtonLabel) + } } func setupLayout() { + switch currentClipType { + case .allClip: + setupAllClipLayout() + case .anthoerClip: + setupAnthoerClipLayout() + } + } + + func setupAllClipLayout() { + editButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(20) + $0.top.equalToSuperview() + $0.height.equalTo(54) + } + + deleteButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(20) + $0.top.equalTo(editButton.snp.bottom).offset(1) + $0.height.equalTo(54) + } + + [editButtonLabel, deleteButtonLabel].forEach { + $0.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().inset(20) + } + } + } + + func setupAnthoerClipLayout() { editButton.snp.makeConstraints { $0.leading.trailing.equalToSuperview().inset(20) $0.top.equalToSuperview() diff --git a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift index bd692856..4785e90c 100644 --- a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift +++ b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift @@ -23,7 +23,7 @@ final class DetailClipViewController: UIViewController { private let detailClipEmptyView = DetailClipEmptyView() private let detailClipListCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) - private let linkOptionBottomSheetView = LinkOptionBottomSheetView() + private lazy var linkOptionBottomSheetView = LinkOptionBottomSheetView(currentClipType: ClipType(categoryId: viewModel.categoryId)) private lazy var optionBottom = ToasterBottomSheetViewController(bottomType: .gray, bottomTitle: "더보기", insertView: linkOptionBottomSheetView) @@ -72,7 +72,7 @@ extension DetailClipViewController { func setupCategory(id: Int, name: String) { viewModel.categoryId = id viewModel.categoryName = name - changeClipViewModel.setupCateogry(id) + changeClipViewModel.setupCategory(id) } } @@ -348,7 +348,7 @@ extension DetailClipViewController: DetailClipListCollectionViewCellDelegate { func modifiedButtonTapped(toastId: Int) { viewModel.toastId = toastId changeClipViewModel.setupToastId(toastId) - optionBottom.setupSheetPresentation(bottomHeight: 280) + optionBottom.setupSheetPresentation(bottomHeight: viewModel.categoryId == 0 ? 226 : 280) present(optionBottom, animated: true) } } From 52740e2bb92351e5519cd36f4c9cf7ddbfb6b6ed Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sun, 3 Nov 2024 21:31:00 +0900 Subject: [PATCH 79/98] =?UTF-8?q?[Feat]=20#220=20-=20=ED=81=B4=EB=A6=BD=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20ToolTip=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ToasterTipView/ToasterTipView.swift | 1 + .../Component/LinkOptionBottomSheetView.swift | 2 +- .../View/DetailClipViewController.swift | 17 +++++++++++++++++ .../ViewController/LinkWebViewController.swift | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift index 92a9f163..25c6fc36 100644 --- a/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift +++ b/TOASTER-iOS/Global/Components/ToasterTipView/ToasterTipView.swift @@ -145,6 +145,7 @@ private extension ToasterTipView { tipLabel.do { $0.text = title + $0.numberOfLines = 2 $0.font = .suitMedium(size: 12) $0.textColor = .toasterWhite $0.textAlignment = .center diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift index b2dde550..e2bd5288 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/LinkOptionBottomSheetView.swift @@ -37,7 +37,7 @@ final class LinkOptionBottomSheetView: UIView { private let changeClipButton = UIButton() private let deleteButtonLabel = UILabel() private let editButtonLabel = UILabel() - private let changeClipButtonLabel = UILabel() + private(set) var changeClipButtonLabel = UILabel() // MARK: - Life Cycles diff --git a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift index 4785e90c..67a3f5c7 100644 --- a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift +++ b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift @@ -38,6 +38,12 @@ final class DetailClipViewController: UIViewController { bottomTitle: "클립을 선택해 주세요", insertView: changeClipBottomSheetView) + private lazy var firstToolTip = ToasterTipView( + title: "링크를 다른 클립으로\n이동할 수 있어요!", + type: .right, + sourceItem: linkOptionBottomSheetView.changeClipButtonLabel + ) + private let changeClipSubject = PassthroughSubject() private let selectedClipSubject = PassthroughSubject() private let completeButtonSubject = PassthroughSubject() @@ -201,6 +207,16 @@ private extension DetailClipViewController { } .store(in: cancelBag) } + + func setupToolTip() { + if UserDefaults.standard.value(forKey: TipUserDefaults.isShowDetailClipViewToolTip) == nil { + UserDefaults.standard.set(true, forKey: TipUserDefaults.isShowDetailClipViewToolTip) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.linkOptionBottomSheetView.addSubview(self?.firstToolTip ?? UIView()) + self?.firstToolTip.showToolTipAndDismissAfterDelay(duration: 4) + } + } + } } // MARK: - CollectionView DataSource @@ -350,6 +366,7 @@ extension DetailClipViewController: DetailClipListCollectionViewCellDelegate { changeClipViewModel.setupToastId(toastId) optionBottom.setupSheetPresentation(bottomHeight: viewModel.categoryId == 0 ? 226 : 280) present(optionBottom, animated: true) + if viewModel.categoryId != 0 { setupToolTip() } } } diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 64429b0d..8585c725 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -203,7 +203,7 @@ private extension LinkWebViewController { if UserDefaults.standard.value(forKey: TipUserDefaults.isShowLinkWebViewToolTip) == nil { UserDefaults.standard.set(true, forKey: TipUserDefaults.isShowLinkWebViewToolTip) - DispatchQueue.main.asyncAfter(deadline: .now()+1) { [weak self] in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in guard let self else { return } self.view.addSubview(self.secondToolTip) self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) { From 27040610e79971c74699d7e53b4eba8477ec5c7a Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Mon, 4 Nov 2024 22:44:01 +0900 Subject: [PATCH 80/98] =?UTF-8?q?[Feat]=20#220=20-=20=ED=88=B4=ED=8C=81=20?= =?UTF-8?q?2=EA=B0=9C=EC=9D=BC=20=EB=95=8C=20=EC=8B=9C=EA=B0=84=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 --- .../LinkWeb/ViewController/LinkWebViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift index 8585c725..8cdca88c 100644 --- a/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift +++ b/TOASTER-iOS/Present/LinkWeb/ViewController/LinkWebViewController.swift @@ -206,9 +206,9 @@ private extension LinkWebViewController { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in guard let self else { return } self.view.addSubview(self.secondToolTip) - self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) { + self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 2) { self.view.addSubview(self.firstToolTip) - self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 4) + self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 3) } } } From e724260517b4ee8578017b1e329f338e57b85d9c Mon Sep 17 00:00:00 2001 From: Genesis Date: Tue, 5 Nov 2024 10:26:33 +0900 Subject: [PATCH 81/98] =?UTF-8?q?[Feat]=20#226=20-=20=ED=81=B4=EB=A6=BD?= =?UTF-8?q?=EC=9D=B4=201=EA=B0=9C=20=EC=A1=B4=EC=9E=AC=ED=95=A0=EB=95=8C?= =?UTF-8?q?=20nil=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailClip/ViewModel/ChangeClipViewModel.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift index e34edca1..cc89c7b9 100644 --- a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift +++ b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift @@ -22,7 +22,7 @@ final class ChangeClipViewModel: ViewModelType { } struct Output { - let clipData: AnyPublisher<[SelectClipModel], Never> + let clipData: AnyPublisher<[SelectClipModel]?, Never> let isCompleteButtonEnable: AnyPublisher let changeCategoryResult: AnyPublisher } @@ -31,19 +31,25 @@ final class ChangeClipViewModel: ViewModelType { /// 클립이동 버튼이 눌렸을때 동작 let clipDataPublisher = input.changeButtonTap - .flatMap { [weak self] _ -> AnyPublisher<[SelectClipModel], Never> in + .flatMap { [weak self] _ -> AnyPublisher<[SelectClipModel]?, Never> in guard let self else { return Just([]).eraseToAnyPublisher() } return self.getAllCategoryAPI() - .map { [weak self] result -> [SelectClipModel] in + .map { [weak self] result -> [SelectClipModel]? in guard let self = self else { return [] } + + // 2개 이하일 경우 nil 반환 + if result.count < 2 { + return nil + } + let sortedResult = self.sortCurrentCategoryToTop(result) self.collectionViewHeight = self.calculateCollectionViewHeight(numberOfItems: sortedResult.count) return sortedResult } - .catch { error -> AnyPublisher<[SelectClipModel], Never> in + .catch { error -> AnyPublisher<[SelectClipModel]?, Never> in print("Error: \(error)") return Just([]).eraseToAnyPublisher() } From 18f7039ce900aca4f9bffa7b18d09f1f82b7e7c7 Mon Sep 17 00:00:00 2001 From: Genesis Date: Tue, 5 Nov 2024 10:33:04 +0900 Subject: [PATCH 82/98] =?UTF-8?q?[Feat]=20#226=20-=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=9D=B4=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=84=EB=95=8C=EC=9D=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/DetailClipViewController.swift | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift index 4785e90c..2fd70185 100644 --- a/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift +++ b/TOASTER-iOS/Present/DetailClip/View/DetailClipViewController.swift @@ -165,8 +165,27 @@ private extension DetailClipViewController { output.clipData .receive(on: DispatchQueue.main) .sink { [weak self] clipData in - self?.changeClipBottomSheetView.dataSourceHandler = { clipData } - self?.changeClipBottomSheetView.reloadChangeClipBottom() + guard let self else { return } + + self.dismiss(animated: true) { + // 이동할 클립이 2개 이상일 때 (전체클립 제외) + if let data = clipData { + self.dismiss(animated: true) { + self.changeClipBottom.setupSheetPresentation(bottomHeight: self.changeClipViewModel.collectionViewHeight + 180) + self.present(self.changeClipBottom, animated: true) + } + + self.changeClipBottomSheetView.dataSourceHandler = { data } + self.changeClipBottomSheetView.reloadChangeClipBottom() + + } else { // 현재 클립이 1개 존재할 때 (전체클립 제외) + DispatchQueue.main.asyncAfter(deadline: .now()) { + self.showToastMessage(width: 284, + status: .warning, + message: "이동할 클립을 하나 이상 생성해 주세요") + } + } + } } .store(in: cancelBag) @@ -235,11 +254,6 @@ extension DetailClipViewController: UICollectionViewDataSource { // "클립이동" 클릭 시 linkOptionBottomSheetView.setupChangeClipBottomSheetButtonAction { self.changeClipSubject.send() - - self.dismiss(animated: true) { - self.changeClipBottom.setupSheetPresentation(bottomHeight: self.changeClipViewModel.collectionViewHeight + 185) - self.present(self.changeClipBottom, animated: true) - } } // "삭제" 클릭 시 From e1bedea1d43021e22c30c8c146f291183826b573 Mon Sep 17 00:00:00 2001 From: Genesis Date: Tue, 5 Nov 2024 10:33:29 +0900 Subject: [PATCH 83/98] =?UTF-8?q?[Feat]=20#226=20-=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=97=90=20=EB=A1=9C=EB=94=A9=20=EC=95=A1=EC=85=98=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 --- .../View/Component/ChangeClipBottomSheetView.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift index 2f8e80af..8d4f1206 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift @@ -105,7 +105,15 @@ private extension ChangeClipBottomSheetView { } @objc func completeBottomuttonTapped(_ sender: UIButton) { - delegate?.completButtonTap() + completeBottomButton.loadingButtonTapped( + loadingTitle: "이동 중...", + loadingAnimationSize: 16, + task: { _ in + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + self.delegate?.completButtonTap() + } + } + ) } } From 0489b617222efc30914a181e6a272d05caecef95 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 5 Nov 2024 17:27:13 +0900 Subject: [PATCH 84/98] =?UTF-8?q?[Chore]=20#223=20-=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Cell/DetailClipListCollectionViewCell.swift | 2 +- TOASTER-iOS/Present/Home/View/HomeViewController.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift b/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift index b6895501..70a33f69 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Cell/DetailClipListCollectionViewCell.swift @@ -93,7 +93,7 @@ extension DetailClipListCollectionViewCell { } func configureCell(forModel: RecentLinkModel, isClipHidden: Bool) { - modifiedButton.isHidden = false + modifiedButton.isHidden = true clipNameLabel.text = forModel.toastTitle linkTitleLabel.text = forModel.toastTitle linkLabel.text = forModel.linkUrl diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index 5e10027f..05168708 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -345,9 +345,9 @@ private extension HomeViewController { @objc func arrowButtonTapped() { - print("ARROW BUTTON TAPPED") - let clipViewController = ClipViewController() - navigationController?.pushViewController(clipViewController, animated: true) + let detailClipViewController = DetailClipViewController() + detailClipViewController.setupCategory(id: 0, name: "전체 클립") + navigationController?.pushViewController(detailClipViewController, animated: true) } } From 3d858b34ae02a6c5ec9fe7e94271fc4c67863746 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 5 Nov 2024 23:51:32 +0900 Subject: [PATCH 85/98] =?UTF-8?q?[Feat]=20#220=20-=20=ED=99=88=EB=B7=B0=20?= =?UTF-8?q?=ED=88=B4=ED=8C=81=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/HomeViewController.swift | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/TOASTER-iOS/Present/Home/View/HomeViewController.swift b/TOASTER-iOS/Present/Home/View/HomeViewController.swift index 70e0c1ae..d272a83e 100644 --- a/TOASTER-iOS/Present/Home/View/HomeViewController.swift +++ b/TOASTER-iOS/Present/Home/View/HomeViewController.swift @@ -22,12 +22,7 @@ final class HomeViewController: UIViewController { bottomTitle: "클립 추가", insertView: addClipBottomSheetView) -// private lazy var firstToolTip = ToasterTipView( -// title: "마지막으로 저장한 링크를\n확인하러 가보세요!", -// type: .left, -// sourceItem: navigationView.addressLabel -// ) - + private var firstToolTip: ToasterTipView? private lazy var secondToolTip: ToasterTipView? = { guard let tabBarItems = tabBarController?.tabBar.items else { return nil } let firstTabItem = tabBarItems[3] @@ -192,6 +187,13 @@ extension HomeViewController: UICollectionViewDataSource { header.configureHeader(forTitle: nickName, num: indexPath.section) header.arrowButton.addTarget(self, action: #selector(arrowButtonTapped), for: .touchUpInside) + + // 컬뷰 헤더에 붙는 팁뷰 + firstToolTip = ToasterTipView( + title: "마지막으로 저장한 링크를\n확인하러 가보세요!", + type: .left, + sourceItem: header.arrowButton + ) case 2: header.configureHeader(forTitle: "이주의 링크", num: indexPath.section) @@ -287,21 +289,18 @@ private extension HomeViewController { func setupToolTip() { guard let secondToolTip else { return } - - view.addSubview(secondToolTip) - secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) -// if UserDefaults.standard.value(forKey: TipUserDefaults.isShowHomeViewToolTip) == nil { -// UserDefaults.standard.set(true, forKey: TipUserDefaults.isShowHomeViewToolTip) -// -// DispatchQueue.main.asyncAfter(deadline: .now()+1) { [weak self] in -// guard let self else { return } -// self.view.addSubview(self.secondToolTip) -// self.secondToolTip.showToolTipAndDismissAfterDelay(duration: 4) { -// self.view.addSubview(self.firstToolTip) -// self.firstToolTip.showToolTipAndDismissAfterDelay(duration: 4) -// } -// } -// } + if UserDefaults.standard.value(forKey: TipUserDefaults.isShowHomeViewToolTip) == nil { + UserDefaults.standard.set(true, forKey: TipUserDefaults.isShowHomeViewToolTip) + + DispatchQueue.main.asyncAfter(deadline: .now()+1) { [weak self] in + guard let self else { return } + self.view.addSubview(self.firstToolTip ?? UIView()) + self.firstToolTip?.showToolTipAndDismissAfterDelay(duration: 2) { + self.view.addSubview(self.secondToolTip ?? UIView()) + self.secondToolTip?.showToolTipAndDismissAfterDelay(duration: 3) + } + } + } } func reloadCollectionView(isHidden: Bool) { From 23ac3e1c18d595242251f4bc4c209620a9b7c3c5 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Tue, 5 Nov 2024 23:52:15 +0900 Subject: [PATCH 86/98] =?UTF-8?q?[Setting]=20#220=20-=20identity=20?= =?UTF-8?q?=EC=95=B1=20=EB=B2=84=EC=A0=84=201.1.1=20->=201.2.0=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 --- TOASTER-iOS.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS.xcodeproj/project.pbxproj b/TOASTER-iOS.xcodeproj/project.pbxproj index e7051d68..1770b6b6 100644 --- a/TOASTER-iOS.xcodeproj/project.pbxproj +++ b/TOASTER-iOS.xcodeproj/project.pbxproj @@ -2461,7 +2461,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.0; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "TeamLinkMIND.TOASTER-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -2496,7 +2496,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.0; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "TeamLinkMIND.TOASTER-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; From b4067ff79297b66813897db313814a892b51e646 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Wed, 6 Nov 2024 13:25:00 +0900 Subject: [PATCH 87/98] =?UTF-8?q?[Fix]=20#229=20-=20Pasteboard=EB=A1=9C=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20=EC=8B=9C=20=EC=B2=AB=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=20=ED=99=9C=EC=84=B1=ED=99=94=20=EC=95=88=EB=90=98=EB=8D=98=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddLink/LinkEmbed/View/AddLinkViewController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index 910115c8..c7107f5d 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -69,10 +69,11 @@ extension AddLinkViewController { delegate = forDelegate } - // 클립보드 붙여넣기 Alert -> 붙여넣기 허용 클릭 후 자동 링크 임베드를 위한 함수 + /// 클립보드 붙여넣기 Alert -> 붙여넣기 허용 클릭 후 자동 링크 임베드를 위한 함수 func embedURL(url: String) { addLinkView.linkEmbedTextField.becomeFirstResponder() - addLinkView.linkEmbedTextField.text = url + addLinkView.linkEmbedTextField.text = url // 텍스트필드에 text 채우기 + viewModel.inputs.embedLinkText(url) // 관리중 ViewModel에도 String 수정 -> UI 반영 } } From 35dc0e53b97cf5c99a1866f24c6202d55cf97fb3 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Thu, 7 Nov 2024 21:41:49 +0900 Subject: [PATCH 88/98] =?UTF-8?q?[Fix]=20#229=20-=20=EB=B0=B1=EA=B7=B8?= =?UTF-8?q?=EB=9D=BC=EC=9A=B4=EB=93=9C=20UIPasteboard=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=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 --- TOASTER-iOS/Application/SceneDelegate.swift | 9 +++------ .../AddLink/LinkEmbed/View/AddLinkViewController.swift | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/TOASTER-iOS/Application/SceneDelegate.swift b/TOASTER-iOS/Application/SceneDelegate.swift index 908dc812..c8179c4b 100644 --- a/TOASTER-iOS/Application/SceneDelegate.swift +++ b/TOASTER-iOS/Application/SceneDelegate.swift @@ -63,25 +63,22 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let appDelegate = UIApplication.shared.delegate as! AppDelegate - if let pasteboardString = UIPasteboard.general.url { + if let pasteboardUrl = UIPasteboard.general.url { if appDelegate.isLogin { guard let rootVC = window?.rootViewController as? ToasterNavigationController else { return } let addLinkViewController = AddLinkViewController() rootVC.pushViewController(addLinkViewController, animated: true) - addLinkViewController.embedURL(url: UIPasteboard.general.string ?? "") + addLinkViewController.embedURL(url: pasteboardUrl.absoluteString) if let presentedVC = rootVC.presentedViewController { presentedVC.dismiss(animated: false) } } } - UIPasteboard.general.string = nil } func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. + UIPasteboard.general.url = nil } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index c7107f5d..ea4a50b1 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -74,6 +74,7 @@ extension AddLinkViewController { addLinkView.linkEmbedTextField.becomeFirstResponder() addLinkView.linkEmbedTextField.text = url // 텍스트필드에 text 채우기 viewModel.inputs.embedLinkText(url) // 관리중 ViewModel에도 String 수정 -> UI 반영 + UIPasteboard.general.url = nil } } From f0dec232f92d9ce77a5ebc0464fc55ad6a04b5c3 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sat, 9 Nov 2024 15:55:39 +0900 Subject: [PATCH 89/98] =?UTF-8?q?[Fix]=20#231=20-=20Share=20Extension=20?= =?UTF-8?q?=EC=B5=9C=EC=B4=88=20=EC=A7=84=EC=9E=85=20=EC=8B=9C=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=ED=81=B4=EB=A6=BD=20=EC=84=A0=ED=83=9D=EB=90=9C=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Cell/RemindSelectClipCollectionViewCell.swift | 2 ++ ToasterShareExtension/ShareViewController.swift | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift b/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift index e93521aa..cd19683b 100644 --- a/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift +++ b/TOASTER-iOS/Present/RemindAdd/ClipAdd/View/Cell/RemindSelectClipCollectionViewCell.swift @@ -80,6 +80,8 @@ extension RemindSelectClipCollectionViewCell { func configureCell(forModel: RemindClipModel, icon: UIImage, isRounded: Bool) { clipTitleLabel.text = forModel.title clipCountLabel.text = "\(forModel.clipCount)개" + clipTitleLabel.textColor = isSelected == true ? .toasterPrimary : .black850 + clipCountLabel.textColor = isSelected == true ? .toasterPrimary : .gray600 clipImageView.image = isSelected == true ? icon.withTintColor(.toasterPrimary) : icon self.isRounded = isRounded } diff --git a/ToasterShareExtension/ShareViewController.swift b/ToasterShareExtension/ShareViewController.swift index 1672e7e2..83c5d20b 100644 --- a/ToasterShareExtension/ShareViewController.swift +++ b/ToasterShareExtension/ShareViewController.swift @@ -58,6 +58,16 @@ class ShareViewController: UIViewController { super.viewDidAppear(animated) print("viewDidAppear View height: \(self.view.frame.size.height)") + clipSelectCollectionView.selectItem( + at: IndexPath(row: 0, section: 0), + animated: false, + scrollPosition: .top + ) + collectionView( + clipSelectCollectionView, + didSelectItemAt: IndexPath(row: 0, section: 0) + ) + if isUseShareExtension { // 상단 Title 높이 + 데이터 개수 * cell 높이 + 하단 버튼 + SafeArea let calculateBottomSheetHeight = titleHeight + (viewModel.clipData.count) * 54 + 116 From 9f89b05c5980f3f513a87d44d3ba27b7db7b18af Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sat, 9 Nov 2024 16:07:33 +0900 Subject: [PATCH 90/98] =?UTF-8?q?[Fix]=20#231=20-=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=A4=91=20=EB=B2=84=ED=8A=BC=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20completion=20=ED=95=B8=EB=93=A4=EB=9F=AC?= =?UTF-8?q?=20=ED=98=B8=EC=B6=9C=EB=90=98=EB=8F=84=EB=A1=9D=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 --- .../DetailClip/View/Component/ChangeClipBottomSheetView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift index 8d4f1206..30c8f1ee 100644 --- a/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift +++ b/TOASTER-iOS/Present/DetailClip/View/Component/ChangeClipBottomSheetView.swift @@ -108,9 +108,10 @@ private extension ChangeClipBottomSheetView { completeBottomButton.loadingButtonTapped( loadingTitle: "이동 중...", loadingAnimationSize: 16, - task: { _ in + task: { completion in DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { self.delegate?.completButtonTap() + completion() } } ) From 349a0c6447d57fa1c99fd23f544859cb2f575a36 Mon Sep 17 00:00:00 2001 From: mini-min <2alswo7@khu.ac.kr> Date: Sat, 9 Nov 2024 17:32:27 +0900 Subject: [PATCH 91/98] =?UTF-8?q?[Fix]=20#231=20-=202=EB=B2=88=EC=A7=B8=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=ED=98=B8=EC=B6=9C=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/DetailClip/ViewModel/ChangeClipViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift index cc89c7b9..641d64e9 100644 --- a/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift +++ b/TOASTER-iOS/Present/DetailClip/ViewModel/ChangeClipViewModel.swift @@ -64,7 +64,7 @@ final class ChangeClipViewModel: ViewModelType { /// 완료 버튼이 눌렸을때 동작 let changeCategoryResult = input.completeButtonTap - .combineLatest(input.selectedClip) { _, selectedClip in + .zip(input.selectedClip) { _, selectedClip in return selectedClip } .flatMap { [weak self] selectedClip -> AnyPublisher in From 20456cdb1dd42426141ee32a353a17009670db92 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 12 Nov 2024 01:41:06 +0900 Subject: [PATCH 92/98] =?UTF-8?q?[Refactor]=20#233=20-=20AddLinkViewModel?= =?UTF-8?q?=20ViewModelType=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/AddLinkViewModel.swift | 102 +++++++++--------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index dfd7c4b6..2374e9be 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -5,67 +5,69 @@ // Created by Gahyun Kim on 9/19/24. // +import Combine import UIKit -protocol AddLinkViewModelInputs { - func embedLinkText(_ text: String) -} - -protocol AddLinkViewModelOutputs { - var isClearButtonHidden: Bool { get } - var isNextButtonEnabled: Bool { get } - var nextButtonBackgroundColor: UIColor { get } - var textFieldBorderColor: UIColor { get } - var linkEffectivenessMessage: String? { get } -} - -protocol AddLinkViewModelType { - var inputs: AddLinkViewModelInputs { get } - var outputs: AddLinkViewModelOutputs { get } -} - -final class AddLinkViewModel: AddLinkViewModelType, AddLinkViewModelInputs, AddLinkViewModelOutputs { +final class AddLinkViewModel: ViewModelType { - // Input - private var embedLink: String = "" { - didSet { - updateOutputs() - } - } - - // Output - var isClearButtonHidden: Bool - var isNextButtonEnabled: Bool - var nextButtonBackgroundColor: UIColor - var textFieldBorderColor: UIColor - var linkEffectivenessMessage: String? + private var cancelBag: CancelBag = CancelBag() - init() { - self.isClearButtonHidden = true - self.isNextButtonEnabled = false - self.nextButtonBackgroundColor = .gray200 - self.textFieldBorderColor = .clear - self.linkEffectivenessMessage = nil + struct Input { + let embedLinkText: AnyPublisher } - func embedLinkText(_ text: String) { - embedLink = text + struct Output { + let isClearButtonHidden = PassthroughSubject() + let isNextButtonEnabled = CurrentValueSubject(false) + let nextButtonBackgroundColor = CurrentValueSubject(.gray200) + let textFieldBorderColor = PassthroughSubject() + let linkEffectivenessMessage = PassthroughSubject() } - var inputs: AddLinkViewModelInputs { return self } - var outputs: AddLinkViewModelOutputs { return self } + func transform(_ input: Input, cancelBag: CancelBag) -> Output { + let output = Output() + + input.embedLinkText + .map { $0.isEmpty } + .sink { isHidden in + output.isClearButtonHidden.send(isHidden) + } + .store(in: cancelBag) + + let isValid = input.embedLinkText + .map { self.isValidURL($0) } + .share() + .eraseToAnyPublisher() + + isValid + .combineLatest(input.embedLinkText.map { !$0.isEmpty }) + .map { $0 && $1 } + .sink { isEnabled in + print("활성화 유무 : ", isEnabled) + output.isNextButtonEnabled.send(isEnabled) + output.nextButtonBackgroundColor.send(isEnabled ? .black850 : .gray200) + } + .store(in: cancelBag) + + isValid + .map { $0 ? .clear : UIColor.toasterError } + .sink { color in + output.textFieldBorderColor.send(color) + } + .store(in: cancelBag) + + input.embedLinkText + .map { $0.isEmpty ? "링크를 입력해주세요" : (self.isValidURL($0) ? nil : "유효하지 않은 형식의 링크입니다. " ) } + .sink { message in + output.linkEffectivenessMessage.send(message) + } + .store(in: cancelBag) + + return output + } } private extension AddLinkViewModel { - func updateOutputs() { - let isValid = isValidURL(embedLink) - isClearButtonHidden = embedLink.isEmpty - isNextButtonEnabled = !embedLink.isEmpty && isValid - nextButtonBackgroundColor = isNextButtonEnabled ? .black850 : .gray200 - textFieldBorderColor = isValid ? .clear : UIColor.toasterError - linkEffectivenessMessage = isValid ? nil : (embedLink.isEmpty ? "링크를 입력해주세요" : "유효하지 않은 형식의 링크입니다.") - } - func isValidURL(_ urlString: String) -> Bool { if (urlString.prefix(8) == "https://") || (urlString.prefix(7) == "http://") { return true From 0675dc1ebfcb6e74bbf355a0210de572c074797f Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 12 Nov 2024 01:41:35 +0900 Subject: [PATCH 93/98] =?UTF-8?q?[Refactor]=20#233=20-=20bindViewModels=20?= =?UTF-8?q?Combine=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/AddLinkViewController.swift | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index ea4a50b1..fdf3fa93 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -5,6 +5,7 @@ // Created by 김다예 on 12/30/23. // +import Combine import UIKit import SnapKit @@ -30,22 +31,19 @@ final class AddLinkViewController: UIViewController { private weak var delegate: AddLinkViewControllerPopDelegate? private weak var urldelegate: SelectClipViewControllerDelegate? - // MARK: - UI Components - private var addLinkView = AddLinkView() private var viewModel = AddLinkViewModel() + private var cancelBag = CancelBag() // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() + bindViewModels() setupStyle() setupAddLinkVew() hideKeyboard() - - setupBinding() - updateUI() } override func viewWillAppear(_ animated: Bool) { @@ -73,7 +71,7 @@ extension AddLinkViewController { func embedURL(url: String) { addLinkView.linkEmbedTextField.becomeFirstResponder() addLinkView.linkEmbedTextField.text = url // 텍스트필드에 text 채우기 - viewModel.inputs.embedLinkText(url) // 관리중 ViewModel에도 String 수정 -> UI 반영 + addLinkView.linkEmbedTextField.sendActions(for: .editingChanged) UIPasteboard.general.url = nil } } @@ -135,31 +133,52 @@ private extension AddLinkViewController { } -// ViewModel extension AddLinkViewController { - private func setupBinding() { - addLinkView.linkEmbedTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) - } - - @objc private func textFieldDidChange(_ textField: UITextField) { - viewModel.inputs.embedLinkText(textField.text ?? "") - updateUI() - } - - private func updateUI() { - addLinkView.clearButton.isHidden = viewModel.outputs.isClearButtonHidden - addLinkView.nextTopButton.isEnabled = viewModel.outputs.isNextButtonEnabled - addLinkView.nextTopButton.backgroundColor = viewModel.outputs.nextButtonBackgroundColor - addLinkView.nextBottomButton.isEnabled = viewModel.outputs.isNextButtonEnabled - addLinkView.nextBottomButton.backgroundColor = viewModel.outputs.nextButtonBackgroundColor - addLinkView.linkEmbedTextField.layer.borderColor = viewModel.outputs.textFieldBorderColor.cgColor - addLinkView.linkEmbedTextField.layer.borderWidth = 1 + private func bindViewModels() { + let embedLinkText = addLinkView.linkEmbedTextField + .publisher(for: .editingChanged) + .compactMap { [weak self] _ in self?.addLinkView.linkEmbedTextField.text ?? "" } + .eraseToAnyPublisher() - if let errorMessage = viewModel.outputs.linkEffectivenessMessage { - addLinkView.isValidLinkError(errorMessage) - } else { - addLinkView.resetError() - } + let input = AddLinkViewModel.Input(embedLinkText: embedLinkText) + let output = viewModel.transform(input, cancelBag: cancelBag) + + output.isClearButtonHidden + .sink { isHidden in + self.addLinkView.clearButton.isHidden = isHidden + } + .store(in: cancelBag) + + output.isNextButtonEnabled + .sink { isEnabled in + self.addLinkView.nextTopButton.isEnabled = isEnabled + self.addLinkView.nextBottomButton.isEnabled = isEnabled + } + .store(in: cancelBag) + + output.nextButtonBackgroundColor + .sink { color in + self.addLinkView.nextTopButton.backgroundColor = color + self.addLinkView.nextBottomButton.backgroundColor = color + } + .store(in: cancelBag) + + output.linkEffectivenessMessage + .sink { message in + if let errorMessage = message { + self.addLinkView.isValidLinkError(errorMessage) + } else { + self.addLinkView.resetError() + } + } + .store(in: cancelBag) + + output.textFieldBorderColor + .sink { color in + self.addLinkView.linkEmbedTextField.layer.borderColor = color.cgColor + self.addLinkView.linkEmbedTextField.layer.borderWidth = 1 + } + .store(in: cancelBag) } } From b2d813862c9d98411a8a8d9b7805f7989fd58a87 Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Tue, 12 Nov 2024 17:50:07 +0900 Subject: [PATCH 94/98] [Chore] #233 - clearButtonTapped action --- .../LinkEmbed/View/AddLinkViewController.swift | 6 +++++- .../LinkEmbed/ViewModel/AddLinkViewModel.swift | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index fdf3fa93..7025fca1 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -140,7 +140,11 @@ extension AddLinkViewController { .compactMap { [weak self] _ in self?.addLinkView.linkEmbedTextField.text ?? "" } .eraseToAnyPublisher() - let input = AddLinkViewModel.Input(embedLinkText: embedLinkText) + let clearButtonTapped = addLinkView.clearButton.publisher(for: .touchUpInside) + .map { _ in } + .eraseToAnyPublisher() + + let input = AddLinkViewModel.Input(embedLinkText: embedLinkText, clearButtonTapped: clearButtonTapped) let output = viewModel.transform(input, cancelBag: cancelBag) output.isClearButtonHidden diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index 2374e9be..c23441b5 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -14,6 +14,7 @@ final class AddLinkViewModel: ViewModelType { struct Input { let embedLinkText: AnyPublisher + let clearButtonTapped: AnyPublisher } struct Output { @@ -27,23 +28,26 @@ final class AddLinkViewModel: ViewModelType { func transform(_ input: Input, cancelBag: CancelBag) -> Output { let output = Output() - input.embedLinkText + let inputText = input.embedLinkText + .merge(with: input.clearButtonTapped.map { "" }) + .eraseToAnyPublisher() + + inputText .map { $0.isEmpty } .sink { isHidden in output.isClearButtonHidden.send(isHidden) } .store(in: cancelBag) - let isValid = input.embedLinkText + let isValid = inputText .map { self.isValidURL($0) } .share() .eraseToAnyPublisher() isValid - .combineLatest(input.embedLinkText.map { !$0.isEmpty }) + .combineLatest(inputText.map { !$0.isEmpty }) .map { $0 && $1 } .sink { isEnabled in - print("활성화 유무 : ", isEnabled) output.isNextButtonEnabled.send(isEnabled) output.nextButtonBackgroundColor.send(isEnabled ? .black850 : .gray200) } From 409a0f91797fec4bd59c555d77c8c6dc9580cadf Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 13 Nov 2024 14:35:41 +0900 Subject: [PATCH 95/98] =?UTF-8?q?[Chore]=20#233=20-=20weak=20self=20?= =?UTF-8?q?=EC=B0=B8=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/AddLinkViewController.swift | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index 7025fca1..4dcf283d 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -148,39 +148,39 @@ extension AddLinkViewController { let output = viewModel.transform(input, cancelBag: cancelBag) output.isClearButtonHidden - .sink { isHidden in - self.addLinkView.clearButton.isHidden = isHidden + .sink { [weak self] isHidden in + self?.addLinkView.clearButton.isHidden = isHidden } .store(in: cancelBag) output.isNextButtonEnabled - .sink { isEnabled in - self.addLinkView.nextTopButton.isEnabled = isEnabled - self.addLinkView.nextBottomButton.isEnabled = isEnabled + .sink { [weak self] isEnabled in + self?.addLinkView.nextTopButton.isEnabled = isEnabled + self?.addLinkView.nextBottomButton.isEnabled = isEnabled } .store(in: cancelBag) output.nextButtonBackgroundColor - .sink { color in - self.addLinkView.nextTopButton.backgroundColor = color - self.addLinkView.nextBottomButton.backgroundColor = color + .sink { [weak self] color in + self?.addLinkView.nextTopButton.backgroundColor = color + self?.addLinkView.nextBottomButton.backgroundColor = color } .store(in: cancelBag) output.linkEffectivenessMessage - .sink { message in + .sink { [weak self] message in if let errorMessage = message { - self.addLinkView.isValidLinkError(errorMessage) + self?.addLinkView.isValidLinkError(errorMessage) } else { - self.addLinkView.resetError() + self?.addLinkView.resetError() } } .store(in: cancelBag) output.textFieldBorderColor - .sink { color in - self.addLinkView.linkEmbedTextField.layer.borderColor = color.cgColor - self.addLinkView.linkEmbedTextField.layer.borderWidth = 1 + .sink { [weak self] color in + self?.addLinkView.linkEmbedTextField.layer.borderColor = color.cgColor + self?.addLinkView.linkEmbedTextField.layer.borderWidth = 1 } .store(in: cancelBag) } From 4f5b8924cd743a05977e5e579f1a42dba288a46d Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 13 Nov 2024 14:41:28 +0900 Subject: [PATCH 96/98] =?UTF-8?q?[Chore]=20#233=20-=20Extension=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/AddLink/LinkEmbed/View/AddLinkViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index 4dcf283d..21b9358b 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -141,8 +141,7 @@ extension AddLinkViewController { .eraseToAnyPublisher() let clearButtonTapped = addLinkView.clearButton.publisher(for: .touchUpInside) - .map { _ in } - .eraseToAnyPublisher() + .mapVoid() let input = AddLinkViewModel.Input(embedLinkText: embedLinkText, clearButtonTapped: clearButtonTapped) let output = viewModel.transform(input, cancelBag: cancelBag) From ab2aeb2e2ba36204e90d16bc33208066fc62f1fa Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 13 Nov 2024 16:52:01 +0900 Subject: [PATCH 97/98] =?UTF-8?q?[Chore]=20#233=20-=20Output=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/AddLinkViewController.swift | 19 +++++-------------- .../ViewModel/AddLinkViewModel.swift | 9 --------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index 21b9358b..b8d4bcbe 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -155,14 +155,9 @@ extension AddLinkViewController { output.isNextButtonEnabled .sink { [weak self] isEnabled in self?.addLinkView.nextTopButton.isEnabled = isEnabled + self?.addLinkView.nextTopButton.backgroundColor = isEnabled ? .black850 : .gray200 self?.addLinkView.nextBottomButton.isEnabled = isEnabled - } - .store(in: cancelBag) - - output.nextButtonBackgroundColor - .sink { [weak self] color in - self?.addLinkView.nextTopButton.backgroundColor = color - self?.addLinkView.nextBottomButton.backgroundColor = color + self?.addLinkView.nextBottomButton.backgroundColor = isEnabled ? .black850 : .gray200 } .store(in: cancelBag) @@ -170,18 +165,14 @@ extension AddLinkViewController { .sink { [weak self] message in if let errorMessage = message { self?.addLinkView.isValidLinkError(errorMessage) + self?.addLinkView.linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor + self?.addLinkView.linkEmbedTextField.layer.borderWidth = 1 } else { self?.addLinkView.resetError() + self?.addLinkView.linkEmbedTextField.layer.borderColor = UIColor.clear.cgColor } } .store(in: cancelBag) - - output.textFieldBorderColor - .sink { [weak self] color in - self?.addLinkView.linkEmbedTextField.layer.borderColor = color.cgColor - self?.addLinkView.linkEmbedTextField.layer.borderWidth = 1 - } - .store(in: cancelBag) } } diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index c23441b5..c9da7ced 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -20,7 +20,6 @@ final class AddLinkViewModel: ViewModelType { struct Output { let isClearButtonHidden = PassthroughSubject() let isNextButtonEnabled = CurrentValueSubject(false) - let nextButtonBackgroundColor = CurrentValueSubject(.gray200) let textFieldBorderColor = PassthroughSubject() let linkEffectivenessMessage = PassthroughSubject() } @@ -49,14 +48,6 @@ final class AddLinkViewModel: ViewModelType { .map { $0 && $1 } .sink { isEnabled in output.isNextButtonEnabled.send(isEnabled) - output.nextButtonBackgroundColor.send(isEnabled ? .black850 : .gray200) - } - .store(in: cancelBag) - - isValid - .map { $0 ? .clear : UIColor.toasterError } - .sink { color in - output.textFieldBorderColor.send(color) } .store(in: cancelBag) From 0b94371c31aa1f1316eed9ebc01f51414743cc5e Mon Sep 17 00:00:00 2001 From: mcrkgus Date: Wed, 13 Nov 2024 17:38:30 +0900 Subject: [PATCH 98/98] =?UTF-8?q?[Chore]=20#233=20-=20URL=20=EB=B6=99?= =?UTF-8?q?=EC=97=AC=EB=84=A3=EA=B8=B0=20=ED=9B=84=20button=20=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddLink/LinkEmbed/View/AddLinkViewController.swift | 9 +++++++-- .../AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift index b8d4bcbe..fc37e963 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkViewController.swift @@ -70,8 +70,13 @@ extension AddLinkViewController { /// 클립보드 붙여넣기 Alert -> 붙여넣기 허용 클릭 후 자동 링크 임베드를 위한 함수 func embedURL(url: String) { addLinkView.linkEmbedTextField.becomeFirstResponder() - addLinkView.linkEmbedTextField.text = url // 텍스트필드에 text 채우기 - addLinkView.linkEmbedTextField.sendActions(for: .editingChanged) + addLinkView.linkEmbedTextField.text = url + viewModel.embedLinkText.send(url) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.addLinkView.linkEmbedTextField.sendActions(for: .editingChanged) + } + UIPasteboard.general.url = nil } } diff --git a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift index c9da7ced..8f2d6840 100644 --- a/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift +++ b/TOASTER-iOS/Present/AddLink/LinkEmbed/ViewModel/AddLinkViewModel.swift @@ -12,6 +12,8 @@ final class AddLinkViewModel: ViewModelType { private var cancelBag: CancelBag = CancelBag() + let embedLinkText = PassthroughSubject() + struct Input { let embedLinkText: AnyPublisher let clearButtonTapped: AnyPublisher