From 6dbcdd296f0a126f7285fc35290045478769c1b7 Mon Sep 17 00:00:00 2001 From: taipaise Date: Tue, 5 Aug 2025 22:18:53 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20section=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Setting/View/SettingView.swift | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/Projects/Presentation/Sources/Setting/View/SettingView.swift b/Projects/Presentation/Sources/Setting/View/SettingView.swift index 6dccbd98..c2bd55b2 100644 --- a/Projects/Presentation/Sources/Setting/View/SettingView.swift +++ b/Projects/Presentation/Sources/Setting/View/SettingView.swift @@ -25,14 +25,14 @@ final class SettingView: BaseViewController { } private enum Section: Int, CaseIterable { - case notification +// case notification case information case account var title: String { switch self { - case .notification: - return "알림" +// case .notification: +// return "알림" case .information: return "정보" case .account: @@ -133,31 +133,31 @@ final class SettingView: BaseViewController { } override func bind() { - viewModel.output.generalNotificationEnabled - .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] isEnabled in - let indexPath = IndexPath(row: NotificationSection.general.rawValue, section: Section.notification.rawValue) - guard - let self, - let cell = self.tableView.cellForRow(at: indexPath) as? BitnagilToggleTableViewCell - else { return } - - cell.configureToggleState(isOn: isEnabled) - }) - .store(in: &cancellables) - - viewModel.output.pushNotificationEnabled - .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] isEnabled in - let indexPath = IndexPath(row: NotificationSection.push.rawValue, section: Section.notification.rawValue) - guard - let self, - let cell = self.tableView.cellForRow(at: indexPath) as? BitnagilToggleTableViewCell - else { return } - - cell.configureToggleState(isOn: isEnabled) - }) - .store(in: &cancellables) +// viewModel.output.generalNotificationEnabled +// .receive(on: DispatchQueue.main) +// .sink(receiveValue: { [weak self] isEnabled in +// let indexPath = IndexPath(row: NotificationSection.general.rawValue, section: Section.notification.rawValue) +// guard +// let self, +// let cell = self.tableView.cellForRow(at: indexPath) as? BitnagilToggleTableViewCell +// else { return } +// +// cell.configureToggleState(isOn: isEnabled) +// }) +// .store(in: &cancellables) +// +// viewModel.output.pushNotificationEnabled +// .receive(on: DispatchQueue.main) +// .sink(receiveValue: { [weak self] isEnabled in +// let indexPath = IndexPath(row: NotificationSection.push.rawValue, section: Section.notification.rawValue) +// guard +// let self, +// let cell = self.tableView.cellForRow(at: indexPath) as? BitnagilToggleTableViewCell +// else { return } +// +// cell.configureToggleState(isOn: isEnabled) +// }) +// .store(in: &cancellables) viewModel.output.urlPublisher .receive(on: DispatchQueue.main) @@ -201,8 +201,8 @@ extension SettingView: UITableViewDelegate { let section = Section.allCases[indexPath.section] switch section { - case .notification: - return +// case .notification: +// return case .information: let row = InformationSection.allCases[indexPath.row] switch row { @@ -250,8 +250,8 @@ extension SettingView: UITableViewDelegate { extension SettingView: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Section.allCases[section] { - case .notification: - return NotificationSection.allCases.count +// case .notification: +// return NotificationSection.allCases.count case .information: return InformationSection.allCases.count case .account: @@ -269,9 +269,9 @@ extension SettingView: UITableViewDataSource { let cellStyle: CellStyle switch section { - case .notification: - let row = NotificationSection.allCases[indexPath.row] - cellStyle = row.cellStyle +// case .notification: +// let row = NotificationSection.allCases[indexPath.row] +// cellStyle = row.cellStyle case .information: let row = InformationSection.allCases[indexPath.row] cellStyle = row.cellStyle @@ -306,10 +306,10 @@ extension SettingView: UITableViewDataSource { guard let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingHeaderView.className) as? SettingHeaderView else { return nil } switch section { - case .notification: - let view = UIView() - view.backgroundColor = .white - return view +// case .notification: +// let view = UIView() +// view.backgroundColor = .white +// return view default: headerView.configure(shouldShowDivider: true, title: section.title) return headerView @@ -320,8 +320,8 @@ extension SettingView: UITableViewDataSource { let section = Section.allCases[section] switch section { - case .notification: - return Layout.tableViewTopSpacing +// case .notification: +// return Layout.tableViewTopSpacing default: return Layout.tableViewHeaderHeight } @@ -361,14 +361,14 @@ extension SettingView: BitnagilToggleTableViewCellDelegate { let section = Section.allCases[indexPath.section] switch section { - case .notification: - let row = NotificationSection.allCases[indexPath.row] - switch row { - case .general: - viewModel.action(input: .toggleGeneralNotification) - case .push: - viewModel.action(input: .togglePushNotification) - } +// case .notification: +// let row = NotificationSection.allCases[indexPath.row] +// switch row { +// case .general: +// viewModel.action(input: .toggleGeneralNotification) +// case .push: +// viewModel.action(input: .togglePushNotification) +// } default: break } From f7ee2fd25fb5a60f1aeb15bc93279eadf4383d74 Mon Sep 17 00:00:00 2001 From: taipaise Date: Tue, 5 Aug 2025 23:40:29 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=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 --- .../DataSourceDependencyAssembler.swift | 4 ++ .../Sources/DTO/AppVersionDTO.swift | 15 ++++++ .../Repository/AppConfigRepository.swift | 22 ++++++++ .../AppConfigRepositoryProtocol.swift | 10 ++++ .../Sources/Setting/View/SettingView.swift | 28 +++++++++-- .../Setting/ViewModel/SettingViewModel.swift | 50 +++++++++++++++++-- 6 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 Projects/DataSource/Sources/DTO/AppVersionDTO.swift create mode 100644 Projects/DataSource/Sources/Repository/AppConfigRepository.swift create mode 100644 Projects/Domain/Sources/Protocol/Repository/AppConfigRepositoryProtocol.swift diff --git a/Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift b/Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift index 92f77057..ef2d187b 100644 --- a/Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift +++ b/Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift @@ -36,5 +36,9 @@ public struct DataSourceDependencyAssembler: DependencyAssemblerProtocol { DIContainer.shared.register(type: RoutineRepositoryProtocol.self) { _ in return RoutineRepository() } + + DIContainer.shared.register(type: AppConfigRepositoryProtocol.self) { _ in + return AppConfigRepository() + } } } diff --git a/Projects/DataSource/Sources/DTO/AppVersionDTO.swift b/Projects/DataSource/Sources/DTO/AppVersionDTO.swift new file mode 100644 index 00000000..f8df5fae --- /dev/null +++ b/Projects/DataSource/Sources/DTO/AppVersionDTO.swift @@ -0,0 +1,15 @@ +// +// AppVersionDTO.swift +// DataSource +// +// Created by 이동현 on 8/5/25. +// + +struct AppVersionDTO: Codable { + let resultCount: Int + let results: [AppStoreResultDTO] +} + +struct AppStoreResultDTO: Codable { + let version: String +} diff --git a/Projects/DataSource/Sources/Repository/AppConfigRepository.swift b/Projects/DataSource/Sources/Repository/AppConfigRepository.swift new file mode 100644 index 00000000..bbc5a313 --- /dev/null +++ b/Projects/DataSource/Sources/Repository/AppConfigRepository.swift @@ -0,0 +1,22 @@ +// +// AppConfigRepository.swift +// DataSource +// +// Created by 이동현 on 8/5/25. +// + +import Domain +import Foundation + +final class AppConfigRepository: AppConfigRepositoryProtocol { + + func fetchAppVersion() async throws -> String? { + guard let bundleId = Bundle.main.bundleIdentifier else { return nil } + let urlString = "https://itunes.apple.com/lookup?bundleId=\(bundleId)&country=KR" + guard let url = URL(string: urlString) else { return nil } + + let (data, _) = try await URLSession.shared.data(from: url) + let decoded = try JSONDecoder().decode(AppVersionDTO.self, from: data) + return decoded.results.first?.version + } +} diff --git a/Projects/Domain/Sources/Protocol/Repository/AppConfigRepositoryProtocol.swift b/Projects/Domain/Sources/Protocol/Repository/AppConfigRepositoryProtocol.swift new file mode 100644 index 00000000..ff821cec --- /dev/null +++ b/Projects/Domain/Sources/Protocol/Repository/AppConfigRepositoryProtocol.swift @@ -0,0 +1,10 @@ +// +// AppConfigRepositoryProtocol.swift +// Domain +// +// Created by 이동현 on 8/5/25. +// + +public protocol AppConfigRepositoryProtocol { + func fetchAppVersion() async throws -> String? +} diff --git a/Projects/Presentation/Sources/Setting/View/SettingView.swift b/Projects/Presentation/Sources/Setting/View/SettingView.swift index c2bd55b2..1ed9d07c 100644 --- a/Projects/Presentation/Sources/Setting/View/SettingView.swift +++ b/Projects/Presentation/Sources/Setting/View/SettingView.swift @@ -6,7 +6,9 @@ // import Combine +import Domain import SafariServices +import Shared import SnapKit import UIKit @@ -114,6 +116,11 @@ final class SettingView: BaseViewController { override func configureAttribute() { view.backgroundColor = .white + guard + let authRepository = DIContainer.shared.resolve(type: AuthRepositoryProtocol.self), + let appConfigRepository = DIContainer.shared.resolve(type: AppConfigRepositoryProtocol.self) + else { fatalError("authRepository, appConfigRepository 의존성이 등록되지 않았습니다.") } + tableView.delegate = self tableView.dataSource = self tableView.separatorStyle = .none @@ -122,6 +129,8 @@ final class SettingView: BaseViewController { tableView.register(BitnagilButtonTableViewCell.self, forCellReuseIdentifier: BitnagilButtonTableViewCell.className) tableView.register(BitnagilChevronTableViewCell.self, forCellReuseIdentifier: BitnagilChevronTableViewCell.className) tableView.register(SettingHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingHeaderView.className) + viewModel.configure(authRepository: authRepository, appConfigRepository: appConfigRepository) + viewModel.action(input: .fetchVersion) } override func configureLayout() { @@ -180,9 +189,9 @@ final class SettingView: BaseViewController { switch versionType { case .needUpdate(let version): - cell.configure(title: "버전\(version)", buttonTitle: "업데이트", isButtonEnabled: true) + cell.configure(title: "버전 \(version)", buttonTitle: "업데이트", isButtonEnabled: true) case .latest(let version): - cell.configure(title: "버전\(version)", buttonTitle: "최신", isButtonEnabled: false) + cell.configure(title: "버전 \(version)", buttonTitle: "최신", isButtonEnabled: false) } }) .store(in: &cancellables) @@ -190,7 +199,16 @@ final class SettingView: BaseViewController { viewModel.output.isAuthenticatedPublisher .receive(on: DispatchQueue.main) .sink(receiveValue: { isAuthenticated in - // 로그아웃 완료 후 홈 화면으로 + guard + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let sceneDelegate = windowScene.delegate as? UIWindowSceneDelegate, + let window = sceneDelegate.window + else { return } + + let introView = IntroView() + let navigationController = UINavigationController(rootViewController: introView) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() }) .store(in: &cancellables) } @@ -310,6 +328,10 @@ extension SettingView: UITableViewDataSource { // let view = UIView() // view.backgroundColor = .white // return view + case .information: + let view = UIView() + view.backgroundColor = .white + return view default: headerView.configure(shouldShowDivider: true, title: section.title) return headerView diff --git a/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift b/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift index a8c8d4b6..33d8646c 100644 --- a/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift +++ b/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift @@ -6,6 +6,7 @@ // import Combine +import Domain import Foundation final class SettingViewModel: ViewModel { @@ -35,6 +36,7 @@ final class SettingViewModel: ViewModel { case openURL(type: URLType) case logout case withdrawal + case fetchVersion } struct Output { @@ -46,12 +48,13 @@ final class SettingViewModel: ViewModel { } private(set) var output: Output - private let versionSubject = CurrentValueSubject(.latest(version: "1.0.0")) + private let versionSubject = PassthroughSubject() private let generalNoticeEnabledSubject = CurrentValueSubject(false) private let pushNoticeEnabledSubject = CurrentValueSubject(true) private let externalURLSubject = PassthroughSubject() private let authenticatedSubject = PassthroughSubject() - + private var authRepository: AuthRepositoryProtocol? + private var appConfigRepository: AppConfigRepositoryProtocol? init() { output = .init( @@ -76,9 +79,16 @@ final class SettingViewModel: ViewModel { logout() case .withdrawal: withdrawal() + case .fetchVersion: + fetchVersion() } } + func configure(authRepository: AuthRepositoryProtocol, appConfigRepository: AppConfigRepositoryProtocol) { + self.authRepository = authRepository + self.appConfigRepository = appConfigRepository + } + private func toggleGeneralNotification() { var generalNotificationEnabled = generalNoticeEnabledSubject.value generalNotificationEnabled.toggle() @@ -96,10 +106,42 @@ final class SettingViewModel: ViewModel { } private func logout() { - // TODO: - 로그아웃 api + Task { + do { + try await authRepository?.logout() + authenticatedSubject.send(false) + } catch { + // TODO: - 토스트 팝업 구현 시 + 디자인 추가 시 네트워크 불안정 알림 + } + } } private func withdrawal() { - // TODO: - 회원탈퇴 api + Task { + do { + try await authRepository?.withdraw() + authenticatedSubject.send(false) + } catch { + // TODO: - 토스트 팝업 구현 시 + 디자인 추가 시 네트워크 불안정 알림 + } + } + } + + private func fetchVersion() { + let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + + Task { + do { + let appStoreAppVersion = try await appConfigRepository?.fetchAppVersion() + + if currentVersion != appStoreAppVersion { + versionSubject.send(.needUpdate(version: currentVersion ?? "")) + } else { + versionSubject.send(.latest(version: currentVersion ?? "")) + } + } catch { + + } + } } } From 1e9422b2a52f452f13cb7a52d38009bbedc7d532 Mon Sep 17 00:00:00 2001 From: taipaise Date: Thu, 7 Aug 2025 21:08:04 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=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 --- .../Sources/Setting/View/SettingView.swift | 13 +++++---- .../Setting/ViewModel/SettingViewModel.swift | 27 ++++++++++++++----- SupportingFiles/Info.plist | 1 + 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Projects/Presentation/Sources/Setting/View/SettingView.swift b/Projects/Presentation/Sources/Setting/View/SettingView.swift index 1ed9d07c..b4e07541 100644 --- a/Projects/Presentation/Sources/Setting/View/SettingView.swift +++ b/Projects/Presentation/Sources/Setting/View/SettingView.swift @@ -170,11 +170,14 @@ final class SettingView: BaseViewController { viewModel.output.urlPublisher .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] url in - guard let url else { return } - - let safariView = SFSafariViewController(url: url) - self?.present(safariView, animated: true) + .sink(receiveValue: { [weak self] urlType in + switch urlType { + case .internal(let url): + let safariView = SFSafariViewController(url: url) + self?.present(safariView, animated: true) + case .external(let url): + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } }) .store(in: &cancellables) diff --git a/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift b/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift index 33d8646c..06a86665 100644 --- a/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift +++ b/Projects/Presentation/Sources/Setting/ViewModel/SettingViewModel.swift @@ -11,11 +11,14 @@ import Foundation final class SettingViewModel: ViewModel { enum URLType { + case update case terms case privacy fileprivate var url: URL? { switch self { + case .update: + return URL(string: "itms-apps://itunes.apple.com/app/{빛나길 id}") case .terms: return URL(string: "https://complex-wombat-99f.notion.site/2025-7-20-236f4587491d8071833adfaf8115bce2") case .privacy: @@ -24,6 +27,11 @@ final class SettingViewModel: ViewModel { } } + enum OpenURLType { + case `internal`(url: URL) + case external(url: URL) + } + enum VersionType { case needUpdate(version: String) case latest(version: String) @@ -43,7 +51,7 @@ final class SettingViewModel: ViewModel { let generalNotificationEnabled: AnyPublisher let pushNotificationEnabled: AnyPublisher let versionPublisher: AnyPublisher - let urlPublisher: AnyPublisher + let urlPublisher: AnyPublisher let isAuthenticatedPublisher: AnyPublisher } @@ -51,7 +59,7 @@ final class SettingViewModel: ViewModel { private let versionSubject = PassthroughSubject() private let generalNoticeEnabledSubject = CurrentValueSubject(false) private let pushNoticeEnabledSubject = CurrentValueSubject(true) - private let externalURLSubject = PassthroughSubject() + private let urlSubject = PassthroughSubject() private let authenticatedSubject = PassthroughSubject() private var authRepository: AuthRepositoryProtocol? private var appConfigRepository: AppConfigRepositoryProtocol? @@ -61,7 +69,7 @@ final class SettingViewModel: ViewModel { generalNotificationEnabled: generalNoticeEnabledSubject.eraseToAnyPublisher(), pushNotificationEnabled: pushNoticeEnabledSubject.eraseToAnyPublisher(), versionPublisher: versionSubject.eraseToAnyPublisher(), - urlPublisher: externalURLSubject.eraseToAnyPublisher(), + urlPublisher: urlSubject.eraseToAnyPublisher(), isAuthenticatedPublisher: authenticatedSubject.eraseToAnyPublisher()) } @@ -73,8 +81,8 @@ final class SettingViewModel: ViewModel { togglePushNotification() case .update: updateBitnagil() - case .openURL(type: let type): - externalURLSubject.send(type.url) + case .openURL(let type): + handleURL(type: type) case .logout: logout() case .withdrawal: @@ -102,7 +110,8 @@ final class SettingViewModel: ViewModel { } private func updateBitnagil() { - // TODO: - 앱스토어 열기 + guard let url = URLType.update.url else { return } + urlSubject.send(.external(url: url)) } private func logout() { @@ -144,4 +153,10 @@ final class SettingViewModel: ViewModel { } } } + + private func handleURL(type: URLType) { + guard let url = type.url else { return } + + urlSubject.send(.internal(url: url)) + } } diff --git a/SupportingFiles/Info.plist b/SupportingFiles/Info.plist index 2b30b70c..bfed2c43 100644 --- a/SupportingFiles/Info.plist +++ b/SupportingFiles/Info.plist @@ -34,6 +34,7 @@ LSApplicationQueriesSchemes kakaokompassauth + itms-apps UIAppFonts From c55dc33bd7db88228317edeccbde82cfcdddc987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8B?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Fri, 8 Aug 2025 00:36:44 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20defau?= =?UTF-8?q?lt=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20asset=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20MypageView=EC=97=90=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile_graphic.imageset/Contents.json | 23 ++++++++++++++++++ .../profile_graphic.png | Bin 0 -> 2631 bytes .../profile_graphic@2x.png | Bin 0 -> 5316 bytes .../profile_graphic@3x.png | Bin 0 -> 7988 bytes .../Common/DesignSystem/BitnagilGraphic.swift | 1 + .../Sources/MyPage/View/MypageView.swift | 4 +-- 6 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic@3x.png diff --git a/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/Contents.json new file mode 100644 index 00000000..4b49ac17 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "profile_graphic.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "profile_graphic@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "profile_graphic@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic.png b/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic.png new file mode 100644 index 0000000000000000000000000000000000000000..b6d8a55baef45bbd5cd912fb9a46a3145032e7da GIT binary patch literal 2631 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 literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic@2x.png b/Projects/Presentation/Resources/Images.xcassets/profile_graphic.imageset/profile_graphic@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b6b538b4933830bade24b0f746093afbda3385b GIT binary patch literal 5316 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 qVK;rfTwm(B8% { .settingIcon? .withRenderingMode(.alwaysTemplate) - profileImageView.layer.cornerRadius = Layout.profileImageViewCornerRadius - profileImageView.layer.masksToBounds = true - profileImageView.backgroundColor = BitnagilColor.gray40 // 임시 + profileImageView.image = BitnagilGraphic.profileGraphic nicknameLabel.font = BitnagilFont(style: .title3, weight: .semiBold).font nicknameLabel.textColor = .black From 526dd624eb1726d17ebbe6f8da1e638dee4b73a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8B?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Fri, 8 Aug 2025 00:59:02 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Refactor:=20isAuthenticated=20=EA=B0=92?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=99=94=EB=A9=B4=20=EC=A0=84?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Extension/UIViewController+.swift | 2 +- .../Sources/Setting/View/SettingView.swift | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Projects/Presentation/Sources/Common/Extension/UIViewController+.swift b/Projects/Presentation/Sources/Common/Extension/UIViewController+.swift index e914add0..667cb556 100644 --- a/Projects/Presentation/Sources/Common/Extension/UIViewController+.swift +++ b/Projects/Presentation/Sources/Common/Extension/UIViewController+.swift @@ -31,7 +31,7 @@ extension UIViewController { case .withPrograssBarWithoutBackButton(let step, let stepCount): navigationController?.setNavigationBarHidden(false, animated: false) - navigationController?.navigationItem.setHidesBackButton(true, animated: false) + navigationItem.setHidesBackButton(true, animated: false) configureProgressNavigationBar(step: step, stepCount: stepCount) } } diff --git a/Projects/Presentation/Sources/Setting/View/SettingView.swift b/Projects/Presentation/Sources/Setting/View/SettingView.swift index b4e07541..cb8d2b62 100644 --- a/Projects/Presentation/Sources/Setting/View/SettingView.swift +++ b/Projects/Presentation/Sources/Setting/View/SettingView.swift @@ -202,16 +202,18 @@ final class SettingView: BaseViewController { viewModel.output.isAuthenticatedPublisher .receive(on: DispatchQueue.main) .sink(receiveValue: { isAuthenticated in - guard - let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let sceneDelegate = windowScene.delegate as? UIWindowSceneDelegate, - let window = sceneDelegate.window - else { return } - - let introView = IntroView() - let navigationController = UINavigationController(rootViewController: introView) - window?.rootViewController = navigationController - window?.makeKeyAndVisible() + if !isAuthenticated { + guard + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let sceneDelegate = windowScene.delegate as? UIWindowSceneDelegate, + let window = sceneDelegate.window + else { return } + + let introView = IntroView() + let navigationController = UINavigationController(rootViewController: introView) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + } }) .store(in: &cancellables) }