diff --git a/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/Contents.json new file mode 100644 index 00000000..cb93786e --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "bitnagil_chevron_icon@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bitnagil_chevron_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bitnagil_chevron_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@1x.png b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@1x.png new file mode 100644 index 00000000..111342f6 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@1x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@2x.png new file mode 100644 index 00000000..3958796e Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@3x.png b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@3x.png new file mode 100644 index 00000000..a4e489d2 Binary files /dev/null and b/Projects/Presentation/Resources/Images.xcassets/bitnagil_chevron_icon.imageset/bitnagil_chevron_icon@3x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png b/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png index aece89bf..071b5eb1 100644 Binary files a/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png and b/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png index bb6082bd..03c7e5fa 100644 Binary files a/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png and b/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png differ diff --git a/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png b/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png index d22d9cf0..7d6aa119 100644 Binary files a/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png and b/Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png differ diff --git a/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilButtonTableViewCell.swift b/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilButtonTableViewCell.swift index 3c9c5440..197edc46 100644 --- a/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilButtonTableViewCell.swift +++ b/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilButtonTableViewCell.swift @@ -49,8 +49,8 @@ final class BitnagilButtonTableViewCell: BitnagilBaseTableViewCell { button.isEnabled = isButtonEnabled if isButtonEnabled { - button.backgroundColor = BitnagilColor.lightBlue100 - button.setTitleColor(BitnagilColor.navy500, for: .normal) + button.backgroundColor = BitnagilColor.orange50 + button.setTitleColor(BitnagilColor.orange500, for: .normal) } else { button.backgroundColor = BitnagilColor.gray98 button.setTitleColor(BitnagilColor.gray70, for: .disabled) diff --git a/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilChevronTableViewCell.swift b/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilChevronTableViewCell.swift index a24edbea..14e168ff 100644 --- a/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilChevronTableViewCell.swift +++ b/Projects/Presentation/Sources/Common/Component/TableViewCell/BitnagilChevronTableViewCell.swift @@ -10,8 +10,9 @@ import UIKit final class BitnagilChevronTableViewCell: BitnagilBaseTableViewCell { private enum Layout { - static let chevronImageViewTrailingSpacing: CGFloat = 16 - static let chevronImageViewSize: CGFloat = 16 + static let chevronImageViewTrailingSpacing: CGFloat = 20 + static let chevronImageWidth: CGFloat = 7 + static let chevronImageHeight: CGFloat = 11 } private let chevronImageView = UIImageView() @@ -29,7 +30,7 @@ final class BitnagilChevronTableViewCell: BitnagilBaseTableViewCell { chevronImageView.tintColor = .black chevronImageView.image = BitnagilIcon - .chevronIcon(direction: .right)? + .bitnagilChevronIcon(direction: .right)? .withRenderingMode(.alwaysTemplate) } @@ -41,7 +42,8 @@ final class BitnagilChevronTableViewCell: BitnagilBaseTableViewCell { chevronImageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().inset(Layout.chevronImageViewTrailingSpacing) - make.size.equalTo(Layout.chevronImageViewSize) + make.width.equalTo(Layout.chevronImageWidth) + make.height.equalTo(Layout.chevronImageHeight) } } } diff --git a/Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift b/Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift index 4210250a..edf2dab6 100644 --- a/Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift +++ b/Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift @@ -15,7 +15,10 @@ enum BitnagilIcon { // MARK: - Common Icons static let backButtonIcon = UIImage(named: "back_button_icon", in: bundle, with: nil) static let plusIcon = UIImage(named: "plus_icon", in: bundle, with: nil)?.withRenderingMode(.alwaysTemplate) - + static let bitnagilChevronIcon = UIImage(named: "bitnagil_chevron_icon", in: bundle, with: nil) + static func bitnagilChevronIcon(direction: Direction) -> UIImage? { + return BitnagilIcon.bitnagilChevronIcon?.rotate(degrees: direction.rotation)?.withRenderingMode(.alwaysTemplate) + } // MARK: - Login Icons static let kakaoIcon = UIImage(named: "kakao_icon", in: bundle, with: nil) static let appleIcon = UIImage(named: "apple_icon", in: bundle, with: nil) diff --git a/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift b/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift index 07127f57..c5deaaad 100644 --- a/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift +++ b/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift @@ -28,10 +28,15 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol { guard let emotionUseCase = container.resolve(type: EmotionUseCaseProtocol.self) else { fatalError("emotionUseCase 의존성이 등록되지 않았습니다.") } + guard let appConfigRepository = container.resolve(type: AppConfigRepositoryProtocol.self) + else { fatalError("appConfigRepository 의존성이 등록되지 않았습니다.") } + + return HomeViewModel( routineUseCase: routineUseCase, userDataUseCase: userDataUseCase, - emotionUseCase: emotionUseCase) + emotionUseCase: emotionUseCase, + appConfigRepository: appConfigRepository) } DIContainer.shared.register(type: LoginViewModel.self) { container in diff --git a/Projects/Presentation/Sources/Home/View/HomeViewController.swift b/Projects/Presentation/Sources/Home/View/HomeViewController.swift index 168d8769..e72e8518 100644 --- a/Projects/Presentation/Sources/Home/View/HomeViewController.swift +++ b/Projects/Presentation/Sources/Home/View/HomeViewController.swift @@ -103,6 +103,7 @@ final class HomeViewController: BaseViewController { super.viewDidLoad() showIndicatorView() viewModel.action(input: .loadNickname) + viewModel.action(input: .fetchVersion) } override func viewWillAppear(_ animated: Bool) { @@ -461,6 +462,35 @@ final class HomeViewController: BaseViewController { self?.weekView.updateAllCompletedState(allCompletedDates: allCompletedDates) } .store(in: &cancellables) + + viewModel.output.updateVersionPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] updateURL in + guard let updateURL else { return } + + let alert = UIAlertController( + title: "업데이트가 필요합니다", + message: "원활한 이용을 위해, 빛나길을 업데이트 해주세요!", + preferredStyle: .alert) + + let cancel = UIAlertAction( + title: "취소", + style: .default, + handler: { _ in exit(0) }) + + let update = UIAlertAction( + title: "업데이트", + style: .default, + handler: { _ in + UIApplication.shared.open(updateURL, options: [:], completionHandler: { _ in exit(0) }) + }) + + alert.addAction(cancel) + alert.addAction(update) + alert.preferredAction = update + self?.present(alert, animated: true) + } + .store(in: &cancellables) } // 해당 날짜의 Routine View를 설정합니다. (없다면 EmptyView) diff --git a/Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift b/Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift index 661752fe..cf4b2ef6 100644 --- a/Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift +++ b/Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift @@ -20,6 +20,7 @@ final class HomeViewModel: ViewModel { case fetchDailyRoutine case refreshDailyRoutine case updateRoutineCompletion(updatedRoutine: Routine) + case fetchVersion } struct Output { @@ -31,6 +32,7 @@ final class HomeViewModel: ViewModel { let routinesPublisher: AnyPublisher<[Routine], Never> let updateRoutineCompletionResultPublisher: AnyPublisher let allCompletedRoutineDatePublisher: AnyPublisher<[Date], Never> + let updateVersionPublisher: AnyPublisher } private(set) var output: Output @@ -45,6 +47,7 @@ final class HomeViewModel: ViewModel { private let selectedRoutineSubject = CurrentValueSubject(nil) private let updateRoutineCompletionResultSubject = PassthroughSubject() private let allCompletedRoutineDateSubject = CurrentValueSubject<[Date], Never>([]) + private let updateVersionSubject = PassthroughSubject() private let calendar = Calendar.current private let today = Date() @@ -54,14 +57,18 @@ final class HomeViewModel: ViewModel { private let routineUseCase: RoutineUseCaseProtocol private let userDataUseCase: UserDataUseCaseProtocol private let emotionUseCase: EmotionUseCaseProtocol + private let appConfigRepository: AppConfigRepositoryProtocol + init( routineUseCase: RoutineUseCaseProtocol, userDataUseCase: UserDataUseCaseProtocol, - emotionUseCase: EmotionUseCaseProtocol + emotionUseCase: EmotionUseCaseProtocol, + appConfigRepository: AppConfigRepositoryProtocol ) { self.routineUseCase = routineUseCase self.userDataUseCase = userDataUseCase self.emotionUseCase = emotionUseCase + self.appConfigRepository = appConfigRepository self.output = Output( nicknamePublisher: nicknameSubject.eraseToAnyPublisher(), emotionPublisher: emotionSubject.eraseToAnyPublisher(), @@ -70,7 +77,8 @@ final class HomeViewModel: ViewModel { fetchRoutineResultPublisher: fetchRoutineResultSubject.eraseToAnyPublisher(), routinesPublisher: routinesSubject.eraseToAnyPublisher(), updateRoutineCompletionResultPublisher: updateRoutineCompletionResultSubject.eraseToAnyPublisher(), - allCompletedRoutineDatePublisher: allCompletedRoutineDateSubject.eraseToAnyPublisher()) + allCompletedRoutineDatePublisher: allCompletedRoutineDateSubject.eraseToAnyPublisher(), + updateVersionPublisher: updateVersionSubject.eraseToAnyPublisher()) } func action(input: Input) { @@ -102,6 +110,9 @@ final class HomeViewModel: ViewModel { case .refreshDailyRoutine: refreshSelectedDateRoutines() + + case .fetchVersion: + checkVersion() } } @@ -262,4 +273,26 @@ final class HomeViewModel: ViewModel { } } } + + private func checkVersion() { + let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + let major = currentVersion?.split(separator: ".").first + + Task { + do { + let appStoreAppVersion = try await appConfigRepository.fetchAppVersion() + let appStoreMajor = appStoreAppVersion?.split(separator: ".").first + + if major != appStoreMajor { + let url = URL(string: "itms-apps://itunes.apple.com/app/id6749437799") + updateVersionSubject.send(url) + } else { + updateVersionSubject.send(nil) + } + + } catch { + + } + } + } } diff --git a/Projects/Presentation/Sources/MyPage/View/MypageView.swift b/Projects/Presentation/Sources/MyPage/View/MypageView.swift index 58c94380..da8ccb76 100644 --- a/Projects/Presentation/Sources/MyPage/View/MypageView.swift +++ b/Projects/Presentation/Sources/MyPage/View/MypageView.swift @@ -49,6 +49,7 @@ final class MypageView: BaseViewController { navigationController?.navigationBar.standardAppearance = appearance navigationController?.navigationBar.scrollEdgeAppearance = appearance navigationController?.navigationBar.compactAppearance = appearance + navigationController?.navigationBar.isHidden = false } override func configureAttribute() { @@ -58,11 +59,7 @@ final class MypageView: BaseViewController { settingButton.action = #selector(settingButtonTapped) settingButton.target = self - settingButton.tintColor = .black - settingButton.image = BitnagilIcon - .settingIcon? - .withRenderingMode(.alwaysTemplate) - + settingButton.image = BitnagilIcon.settingIcon?.withRenderingMode(.alwaysOriginal) profileImageView.image = BitnagilGraphic.profileGraphic nicknameLabel.font = BitnagilFont(style: .title3, weight: .semiBold).font @@ -112,12 +109,14 @@ final class MypageView: BaseViewController { override func bind() { viewModel.output.nickNamePublisher + .receive(on: DispatchQueue.main) .sink { [weak self] nickname in self?.nicknameLabel.text = nickname } .store(in: &cancellables) viewModel.output.externalURLPublisher + .receive(on: DispatchQueue.main) .sink { [weak self] url in let safariView = SFSafariViewController(url: url) self?.present(safariView, animated: true) diff --git a/Projects/Presentation/Sources/Setting/View/SettingView.swift b/Projects/Presentation/Sources/Setting/View/SettingView.swift index 99beb661..9b5d6530 100644 --- a/Projects/Presentation/Sources/Setting/View/SettingView.swift +++ b/Projects/Presentation/Sources/Setting/View/SettingView.swift @@ -14,7 +14,7 @@ import UIKit final class SettingView: BaseViewController { private enum Layout { - static let tableViewTopSpacing: CGFloat = 32 + static let tableViewTopSpacing: CGFloat = 80 static let tableViewHeaderHeight: CGFloat = 48 static let tableViewRowHeight: CGFloat = 48 static let tableViewFooterHeight: CGFloat = .zero @@ -110,7 +110,8 @@ final class SettingView: BaseViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) -// configureNavigationBar(navigationStyle: .withBackButton(title: "설정")) + navigationController?.navigationBar.isHidden = true + configureCustomNavigationBar(navigationBarStyle: .withBackButton(title: "설정")) } override func configureAttribute() { @@ -134,10 +135,14 @@ final class SettingView: BaseViewController { } override func configureLayout() { + let safeArea = view.safeAreaLayoutGuide + view.addSubview(tableView) tableView.snp.makeConstraints { make in - make.edges.horizontalEdges.equalToSuperview() + make.top.equalTo(safeArea.snp.top).offset(Layout.tableViewTopSpacing) + make.horizontalEdges.equalToSuperview() + make.bottom.equalTo(safeArea.snp.bottom) } } @@ -337,9 +342,8 @@ extension SettingView: UITableViewDataSource { // view.backgroundColor = .white // return view case .information: - let view = UIView() - view.backgroundColor = .white - return view + headerView.configure(shouldShowDivider: false, title: section.title) + return headerView default: headerView.configure(shouldShowDivider: true, title: section.title) return headerView