-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#470 강제 업데이트 구현 #471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
The head ref may contain hidden characters: "feat/#470-\uAC15\uC81C-\uC5C5\uB370\uC774\uD2B8-\uAD6C\uD604"
Feat/#470 강제 업데이트 구현 #471
Changes from all commits
f5e6bdd
858b273
fdf49ae
cbf93c6
ab077e2
032a201
716c996
bfe9b52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // | ||
| // RemoteConfigeManager.swift | ||
| // ByeBoo-iOS | ||
| // | ||
| // Created by 이나연 on 7/2/26. | ||
| // | ||
|
|
||
| import FirebaseRemoteConfig | ||
|
|
||
| final class DefaultForceUpdateRepository: ForceUpdateInterface { | ||
| let remoteConfig = RemoteConfig.remoteConfig() | ||
|
|
||
| init() { | ||
| let settings = RemoteConfigSettings() | ||
| settings.minimumFetchInterval = 0 | ||
| RemoteConfig.remoteConfig().configSettings = settings | ||
| } | ||
|
|
||
| func checkForUpdate() async -> Bool { | ||
| do { | ||
| try await remoteConfig.fetch() | ||
| try await remoteConfig.activate() | ||
|
|
||
| let minimumVersion = remoteConfig["min_version_code"].stringValue | ||
|
|
||
| return isUpdateRequired(minimumVersion: minimumVersion) | ||
| } catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| private var currentAppVersion: String { | ||
| Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0" | ||
| } | ||
|
|
||
| private func isUpdateRequired(minimumVersion: String) -> Bool { | ||
| ByeBooLogger.debug("현재버전: \(currentAppVersion), 최소버전: \(minimumVersion)") | ||
| return currentAppVersion.compare(minimumVersion, options: .numeric) == .orderedAscending | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // | ||
| // ForeceUpdateInterface.swift | ||
| // ByeBoo-iOS | ||
| // | ||
| // Created by 이나연 on 7/5/26. | ||
| // | ||
|
|
||
| protocol ForceUpdateInterface { | ||
| func checkForUpdate() async -> Bool | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // | ||
| // CheckForceUpdateUseCase.swift | ||
| // ByeBoo-iOS | ||
| // | ||
| // Created by 이나연 on 7/4/26. | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| protocol CheckForceUpdateUseCase { | ||
| func execute() async -> Bool | ||
| } | ||
|
|
||
| struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase { | ||
|
|
||
| private let repository: ForceUpdateInterface | ||
|
|
||
| init(repository: ForceUpdateInterface) { | ||
| self.repository = repository | ||
| } | ||
|
|
||
| func execute() async -> Bool { | ||
| return await repository.checkForUpdate() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // | ||
| // ForceUpdateModalView.swift | ||
| // ByeBoo-iOS | ||
| // | ||
| // Created by 이나연 on 7/4/26. | ||
| // | ||
|
|
||
| import UIKit | ||
|
|
||
| import SnapKit | ||
| import Then | ||
|
|
||
| final class ForceUpdateModalView: BaseView, ModalProtocol { | ||
| let actionButton: ByeBooButton = ByeBooButton(titleText: "업데이트 하러 가기", type: .enabled) | ||
| let dismissButton: ByeBooButton? = nil | ||
| let modalType: ConfirmModalType? = nil | ||
|
|
||
| private let titleLabel = UILabel() | ||
| private let descriptionLabel = UILabel() | ||
|
|
||
| override func setUI() { | ||
| addSubviews(titleLabel, descriptionLabel, actionButton) | ||
| } | ||
|
|
||
| override func setStyle() { | ||
| backgroundColor = .grayscale900 | ||
| layer.cornerRadius = 12 | ||
|
|
||
| titleLabel.applyByeBooFont( | ||
| style: .sub3M18, | ||
| text: "새로운 업데이트가 있어요", | ||
| color: .grayscale50 | ||
| ) | ||
|
|
||
| descriptionLabel.applyByeBooFont( | ||
| style: .body3R16, | ||
| text: "더 나은 바이부를 만나보세요", | ||
| color: .grayscale400 | ||
| ) | ||
| } | ||
|
|
||
| override func setLayout() { | ||
| titleLabel.snp.makeConstraints { | ||
| $0.top.equalToSuperview().inset(24.adjustedH) | ||
| $0.centerX.equalToSuperview() | ||
| } | ||
| descriptionLabel.snp.makeConstraints { | ||
| $0.top.equalTo(titleLabel.snp.bottom).offset(16.adjustedH) | ||
| $0.centerX.equalToSuperview() | ||
| } | ||
| actionButton.snp.makeConstraints { | ||
| $0.top.equalTo(descriptionLabel.snp.bottom).offset(16.adjustedH) | ||
| $0.centerX.equalToSuperview() | ||
| $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) | ||
| $0.bottom.equalToSuperview().inset(24.adjustedH) | ||
| $0.height.equalTo(53.adjustedH) | ||
| } | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // | ||
| // AppConstants.swift | ||
| // ByeBoo-iOS | ||
| // | ||
| // Created by 이나연 on 7/4/26. | ||
| // | ||
|
|
||
| enum AppConstants { | ||
| static let appStoreID = "6748205348" | ||
| static let appStoreURL = "itms-apps://itunes.apple.com/app/id\(appStoreID)" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,26 +9,33 @@ import Combine | |
| import UIKit | ||
|
|
||
| final class SplashViewController: BaseViewController { | ||
|
|
||
| private let rootView = SplashView() | ||
| private let viewModel: SplashViewModel | ||
| private var cancellables = Set<AnyCancellable>() | ||
|
|
||
| private var isFirstLaunch = true | ||
|
|
||
| init(viewModel: SplashViewModel) { | ||
| self.viewModel = viewModel | ||
| super.init(nibName: nil, bundle: nil) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| override func viewDidLoad() { | ||
| view = rootView | ||
| viewModel.action(.viewDidLoad) | ||
| bind() | ||
| setAddTarget() | ||
|
|
||
| NotificationCenter.default.addObserver( | ||
| self, | ||
| selector: #selector(appDidBecomeActive), | ||
| name: UIApplication.didBecomeActiveNotification, | ||
| object: nil | ||
| ) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in | ||
| guard let self = self else { return } | ||
| if self.cancellables.isEmpty { | ||
|
|
@@ -39,8 +46,27 @@ final class SplashViewController: BaseViewController { | |
| } | ||
|
|
||
| extension SplashViewController { | ||
|
|
||
| private func bind() { | ||
| bindCheckForceUpdate() | ||
| bindAutoLogin() | ||
| } | ||
|
|
||
| private func bindCheckForceUpdate() { | ||
| viewModel.output.forceUpdatePublisher | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { [weak self] result in | ||
| guard let self else { return } | ||
| switch result { | ||
| case true: | ||
| self.presentForceUpdateModal() | ||
| case false: | ||
| self.viewModel.action(.tryAutoLogin) | ||
| } | ||
| } | ||
| .store(in: &cancellables) | ||
| } | ||
|
|
||
| private func bindAutoLogin() { | ||
| viewModel.output.autoLoginPublisher | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { result in | ||
|
|
@@ -77,6 +103,29 @@ extension SplashViewController { | |
| } | ||
| .store(in: &cancellables) | ||
| } | ||
|
|
||
| private func presentForceUpdateModal() { | ||
| let modal = ModalBuilder( | ||
| modalView: ForceUpdateModalView(), | ||
| action: openAppstore, | ||
| rootViewController: self | ||
| ) | ||
| modal.present() | ||
| } | ||
|
|
||
| private func openAppstore() { | ||
| guard let url = URL(string: AppConstants.appStoreURL) else { return } | ||
| UIApplication.shared.open(url) | ||
| } | ||
|
|
||
|
Comment on lines
+106
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# ModalBuilder/ModalProtocol의 배경 탭/스와이프 dismiss 처리 여부 확인
rg -n -B3 -A20 'class ModalBuilder' ByeBoo-iOS/ByeBoo-iOS --type=swift
rg -n 'dismissButton|modalType|tapGesture|backgroundView' ByeBoo-iOS/ByeBoo-iOS --type=swift -g '*Modal*'Repository: 36-APPJAM-HEARTZ/BYEBOO-iOS Length of output: 4217 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== ModalProtocol ==\n'
sed -n '1,120p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/ModalProtocol.swift
printf '\n== CustomModalController ==\n'
sed -n '1,180p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/CustomModalController.swift
printf '\n== ForceUpdateModalView ==\n'
sed -n '1,120p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift
printf '\n== ModalBuilder ==\n'
sed -n '1,160p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ModalBuilder.swiftRepository: 36-APPJAM-HEARTZ/BYEBOO-iOS Length of output: 5627 배경 탭으로 닫히지 않게 막아야 합니다 🤖 Prompt for AI Agents |
||
| @objc | ||
| private func appDidBecomeActive() { | ||
| guard !isFirstLaunch else { | ||
| isFirstLaunch = false | ||
| return | ||
| } | ||
| viewModel.action(.viewDidLoad) | ||
| } | ||
| } | ||
|
|
||
| extension SplashViewController { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.