Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d58795f
[Fix] #280 - LoginViewController 마크 주석 - 표시 추가
youz2me Sep 30, 2025
acf7f00
[Refactor] #280 - TeamCollectionView 메서드 분리 및 단순화, 코드 컨벤션에 맞게 리팩토링
youz2me Sep 30, 2025
96d57b0
[Refactor] #280 - TeamCollectionView init 파라미터 네이밍 수정
youz2me Sep 30, 2025
c393cb2
[Refactor] #280 - LCKTeamViewController Coordinator 패턴 적용 및 컨벤션에 맞게 리팩토링
youz2me Sep 30, 2025
7f4c449
[Refactor] #280 - 온보딩 코디네이터 패턴 적용
youz2me Sep 30, 2025
c2b44c1
[Refactor] #280 - 사진 권한 관련 코드 PermissionManager로 분리
youz2me Sep 30, 2025
20e260d
[Refactor] #280 - 사진 관련 메서드 추가, PhotoPickerHelper로 통합
youz2me Sep 30, 2025
2cfe84e
[Refactor] #280 - PhotoPickerHelper 구현
youz2me Sep 30, 2025
50148b5
[Refactor] #280 - 프로필 이미지 로직 공통화 및 중복 제거
youz2me Sep 30, 2025
ec0bf0b
[Refactor] #280 - ProfileRegisterViewController ViewModel 분리, DI 적용
youz2me Sep 30, 2025
6c19c5d
[Refactor] #280 - ProfileEditViewController 프로필 이미지 로직 공통화 및 중복 제거
youz2me Sep 30, 2025
1b6663f
[Refactor] #280 - 온보딩/프로필 공통 타입 분리
youz2me Oct 1, 2025
fbd1e7c
[Refactor] #280 - 온보딩 UseCase 제거 및 Repository 직접 호출
youz2me Oct 1, 2025
6ca3d61
[Refactor] #280 - 온보딩 ViewController MVVM 패턴 적용
youz2me Oct 1, 2025
545d21c
[Refactor] #280 - ProfileEditViewController MVVM 패턴 적용 및 상태 관리 개선
youz2me Oct 1, 2025
fc7ca43
[Fix] #280 - ProfileEditViewController 닉네임 초기값 표시 버그 수정
youz2me Oct 1, 2025
ed48bd6
[Refactor] #280 - PhotoPickerHelper 불필요한 권한 요청 제거
youz2me Oct 1, 2025
4d7c97e
[Refactor] #280 - Coordinator 메모리 누수 방지 및 완료 패턴 개선
youz2me Oct 1, 2025
8dd6a94
[Fix] #280 - AgreementViewController 뷰 제약 설정 수정
youz2me Oct 1, 2025
6ee9176
[Fix] #280 - AgreementViewModel 세션 업데이트 로직 수정
youz2me Oct 1, 2025
83dbe61
[Refactor] #280 - 리뷰 반영
youz2me Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 71 additions & 23 deletions Wable-iOS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Wable-iOS/App/AppDelegate+InjectDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ extension AppDelegate {
object: UserSessionRepositoryImpl(userDefaults: UserDefaultsStorage())
)

// MARK: - Account

diContainer.register(for: AccountRepository.self, object: AccountRepositoryImpl())


// MARK: - Login

diContainer.register(for: LoginRepository.self, object: LoginRepositoryImpl())
Expand Down
13 changes: 12 additions & 1 deletion Wable-iOS/Core/Coordinator/Coordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ import UIKit
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
var onFinish: (() -> Void)? { get set }

func start()
}
func childDidFinish(_ child: Coordinator?)
}

extension Coordinator {
func childDidFinish(_ child: Coordinator?) {
for (index, coordinator) in childCoordinators.enumerated() where coordinator === child {
childCoordinators.remove(at: index)
break
}
}
}
24 changes: 0 additions & 24 deletions Wable-iOS/Core/Literals/String/StringLiterals+Profile.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ extension StringLiterals {
static let checkInvaildError = "닉네임에 사용할 수 없는 문자가 포함되어 있어요."
static let checkDuplicateError = "이미 사용 중인 닉네임입니다."
static let checkVaildMessage = "사용 가능한 닉네임입니다."
static let nicknamePattern = "^[ㄱ-ㅎ가-힣a-zA-Z0-9]+$"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

정규식 패턴에서 한글 자음 및 모음 처리를 확인해 주세요.

현재 정규식 패턴 ^[ㄱ-ㅎ가-힣a-zA-Z0-9]+$은 한글 자음(ㄱ-ㅎ)은 포함하지만 모음(ㅏ-ㅣ)은 제외하고 있습니다. 다음 사항들을 확인해 주세요:

  1. 자음만으로 이루어진 닉네임 허용 여부: "ㄱㄴㄷ"과 같은 닉네임이 의도적으로 허용되는 것인지 확인이 필요합니다.
  2. 모음 제외 의도 확인: 모음(ㅏ-ㅣ)이 의도적으로 제외된 것인지 확인해 주세요. 불완전한 문자 입력을 방지하기 위한 것이라면 합당하지만, "ㅏㅑㅓ"와 같은 입력도 막게 됩니다.
  3. 길이 검증 처리 확인: 정규식에는 길이 제한이 없으므로, line 19의 "10자리 이내" 제약은 ProfileRegisterViewModel에서 별도로 검증되는지 확인이 필요합니다.

다음 스크립트로 ProfileRegisterViewModel의 길이 검증 로직을 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# Description: ProfileRegisterViewModel에서 닉네임 길이 검증 로직 확인

# ProfileRegisterViewModel에서 닉네임 길이 체크 로직 검색
rg -n -A 5 -B 5 'count|length|characters\.count' --type swift Wable-iOS/Presentation/Onboarding/ViewModel/ProfileRegisterViewModel.swift

Length of output: 1310


정규식 범위 및 길이 검증 보완 필요

  • 패턴 ^[ㄱ-ㅎ가-힣a-zA-Z0-9]+$가 standalone 자음(ㄱ-ㅎ)은 허용하나 모음(ㅏ-ㅣ)은 제외합니다. 자음·모음 단독 입력 허용 여부를 명확히 해주세요.
  • validateNickname(_:)에 10자 이하 길이 제한 검증 로직을 추가해야 합니다.
🤖 Prompt for AI Agents
In Wable-iOS/Core/Literals/String/StringLiterals+ProfileSetting.swift around
line 23, update the nickname regex and length validation: if you intend to allow
standalone Hangul vowels as well as consonants, change the pattern to include
the ㅏ-ㅣ range (e.g. include ㄱ-ㅎㅏ-ㅣ가-힣 in the character class) or if you want to
disallow standalone Jamo remove ㄱ-ㅎ; then in validateNickname(_:) add an
explicit check that the nickname length is <= 10 (return false or fail
validation when length > 10) so both character range and max-length are
enforced.

}
}
160 changes: 160 additions & 0 deletions Wable-iOS/Core/Photo/PhotoPickerHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// PhotoPickerHelper.swift
// Wable-iOS
//
// Created by YOUJIM on 10/1/25.
//

import Combine
import Photos
import PhotosUI
import UIKit

final class PhotoPickerHelper: NSObject {

// MARK: - Property

private var onImageSelected: ((UIImage) -> Void)?
private weak var presentingViewController: UIViewController?

// MARK: - Life Cycle

init(presentingViewController: UIViewController) {
self.presentingViewController = presentingViewController
super.init()
}

func presentPhotoPicker(onImageSelected: @escaping (UIImage) -> Void) {
self.onImageSelected = onImageSelected
showPhotoPicker()
}
}

// MARK: - Private Method

private extension PhotoPickerHelper {
func showPhotoPicker() {
let picker = createPhotoPicker()
presentingViewController?.present(picker, animated: true)
}

func createPhotoPicker() -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 1

let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
return picker
}

func loadImage(from itemProvider: NSItemProvider) {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in
guard let image = image as? UIImage else { return }

DispatchQueue.main.async {
self?.onImageSelected?(image)
}
}
}
}

// MARK: - Helper Method

extension PhotoPickerHelper {
Comment on lines +62 to +64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헬퍼 메서드를 타입 메서드로 구현하신 이유를 들어보고 싶어요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 타입 메서드로 구현된 기능들의 경우 권한 요청 및 사진 저장과 관련된 기능으로, 따로 상태 저장을 할 필요가 없어 인스턴스를 생성할 필요성을 느끼지 못해 타입 메서드로 구현했습니다!

static func requestPhotoLibraryAccess() -> AnyPublisher<Bool, Never> {
let status = PHPhotoLibrary.authorizationStatus(for: .addOnly)

switch status {
case .authorized, .limited:
return Just(true).eraseToAnyPublisher()
case .denied, .restricted:
return Just(false).eraseToAnyPublisher()
case .notDetermined:
return Future<Bool, Never> { promise in
PHPhotoLibrary.requestAuthorization(for: .addOnly) { newStatus in
promise(.success(newStatus == .authorized || newStatus == .limited))
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
@unknown default:
return Just(false).eraseToAnyPublisher()
}
}

static func openSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}

static func showSettingsAlert(
from viewController: UIViewController,
title: String = "설정",
message: String
) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert
)

alert.addAction(UIAlertAction(title: "닫기", style: .default))
alert.addAction(UIAlertAction(title: "권한 설정하기", style: .default) { _ in
openSettings()
})

viewController.present(alert, animated: true)
}

static func saveImageToPhotoLibrary(_ image: UIImage) -> AnyPublisher<Void, Error> {
Future { promise in
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.creationRequestForAsset(from: image)
} completionHandler: { success, error in
if let error = error {
promise(.failure(error))
} else {
promise(.success(()))
}
}
}
.eraseToAnyPublisher()
}

static func saveImage(
_ image: UIImage,
onSuccess: @escaping () -> Void,
onFailure: @escaping (Error) -> Void
) -> AnyCancellable {
requestPhotoLibraryAccess()
.flatMap { isAuthorized -> AnyPublisher<Void, Error> in
guard isAuthorized else {
return Empty().eraseToAnyPublisher()
}
return saveImageToPhotoLibrary(image)
}
.receive(on: DispatchQueue.main)
.sink(
Comment on lines +131 to +138
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

권한 거부 시 사용자 피드백이 누락됩니다

requestPhotoLibraryAccess()false를 반환하면 현재 Empty()를 반환해 스트림이 조용히 종료됩니다. 이 경우 PhotoDetailViewController.saveImage()에서도 성공/실패 콜백이 모두 호출되지 않아 사용자에게 아무런 안내가 전달되지 않습니다. 권한이 없을 때는 명시적으로 실패를 방출해 상위에서 에러 토스트나 설정 이동 안내를 보여줄 수 있도록 해야 합니다.

-                guard isAuthorized else {
-                    return Empty().eraseToAnyPublisher()
-                }
+                guard isAuthorized else {
+                    return Fail(
+                        error: NSError(
+                            domain: "PhotoPickerHelper",
+                            code: -1,
+                            userInfo: [NSLocalizedDescriptionKey: "Photo library access denied"]
+                        )
+                    )
+                    .eraseToAnyPublisher()
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.flatMap { isAuthorized -> AnyPublisher<Void, Error> in
guard isAuthorized else {
return Empty().eraseToAnyPublisher()
}
return saveImageToPhotoLibrary(image)
}
.receive(on: DispatchQueue.main)
.sink(
.flatMap { isAuthorized -> AnyPublisher<Void, Error> in
guard isAuthorized else {
return Fail(
error: NSError(
domain: "PhotoPickerHelper",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Photo library access denied"]
)
)
.eraseToAnyPublisher()
}
return saveImageToPhotoLibrary(image)
}
.receive(on: DispatchQueue.main)
.sink(

receiveCompletion: { completion in
if case .failure(let error) = completion {
onFailure(error)
}
},
receiveValue: { _ in
onSuccess()
}
)
}
}

// MARK: - PHPickerViewControllerDelegate

extension PhotoPickerHelper: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)

guard let result = results.first else { return }
loadImage(from: result.itemProvider)
Comment on lines +155 to +158
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 코드는 순차적으로 처리하셨는데 특별한 이유가 있는지 궁금합니다.

저는 dismiss의 컴플리션 핸들러를 활용해볼 생각이 들었거든요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completionHandler의 경우 코드 뎁스가 깊어져서 정말 필요한 경우가 아니면 사용하지 않았습니다!

}
}

This file was deleted.

27 changes: 0 additions & 27 deletions Wable-iOS/Domain/UseCase/Onboarding/UpdateFCMTokenUseCase.swift

This file was deleted.

64 changes: 0 additions & 64 deletions Wable-iOS/Domain/UseCase/Onboarding/UserProfileUseCase.swift

This file was deleted.

Loading