Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f08ed09
[Add] #182 - 계정 정보 조회 UI 추가
JinUng41 May 12, 2025
cb26461
[Refactor] #182 - 탈퇴하기 로직 수정
JinUng41 May 12, 2025
f6233d4
[Feat] #182 - 알람 설정 화면 및 기능 구현
JinUng41 May 12, 2025
89af4e8
[Refactor] #182 - 뷰모델 형식 변경
JinUng41 May 13, 2025
c32b489
[Refactor] #182 - 뷰모델 형태 변환
JinUng41 May 13, 2025
bf0c54e
[Merge] branch develop into feat/#182-profile
JinUng41 May 13, 2025
1c83c17
[Refactor] #182 - 머지 후 추가 수정
JinUng41 May 13, 2025
7028c72
[Chore] #182 - 변경된 뷰모델 반영
JinUng41 May 13, 2025
dbbfdaa
Merge branch 'develop' of https://github.com/Team-Wable/WABLE-iOS int…
JinUng41 May 13, 2025
43aed0a
[Chore] #182 - 네이밍 변경
JinUng41 May 13, 2025
3e26aea
[Add] #182 - 계정 삭제 이유 화면 추가
JinUng41 May 13, 2025
b96cfde
[Add] #182 - 계정 삭제 안내 ui 추가
JinUng41 May 13, 2025
ae4f9eb
[Style] #182 - 회원 탈퇴 사유 UI 수정
JinUng41 May 13, 2025
46dd12f
[Style] #182 - 계정 정보 UI 수정
JinUng41 May 13, 2025
772678b
[Style] 알림 설정 UI 수정
JinUng41 May 13, 2025
e104cc7
[Add] #182 - 프로필 셀 추가
JinUng41 May 13, 2025
53c19da
[Add] #182 - 마이프로필 ui 추가
JinUng41 May 13, 2025
9872ff0
[Chore] #182 - 임시 커밋
JinUng41 May 14, 2025
b726c69
[Feat] #182 - 프로필 편집 뷰 구현
youz2me May 14, 2025
9670616
[Refactor] #182 - 뷰모델 로직 전면 수정
JinUng41 May 14, 2025
0c2f361
[Fix] #183 - 프로필 수정 기능 구현
youz2me May 14, 2025
f099d68
[Feat] #182 - 로그아웃, 회원탈퇴 구현
JinUng41 May 14, 2025
a5fec53
Merge remote-tracking branch 'refs/remotes/origin/feat/#182-profile'
youz2me May 14, 2025
f6cc929
[Fix] #182 - 충돌 수정
youz2me May 14, 2025
cca5a81
[Feat] #182 - 프로필 편집 화면 이동 구현
youz2me May 14, 2025
c981c62
[Chore] #182 - 사용하지 않는 코드 삭제
JinUng41 May 14, 2025
b7ff92c
[Add] #182 - Dot 이미지 추가
youz2me May 14, 2025
f51e193
Merge remote-tracking branch 'refs/remotes/origin/feat/#182-profile'
youz2me May 14, 2025
70646be
[Fix] #182 - 알림 설정 후, UI 업데이트는 메인 스레드에서 진행하도록 수정
JinUng41 May 19, 2025
4d1c7af
[Fix] #182 - 잘못된 서버 명세 수정
JinUng41 May 19, 2025
574b658
[Refactor] #182 - async/await 도입 후, 로직 적용
JinUng41 May 19, 2025
55b691a
[Chore] #182 - 오탈자 수정
JinUng41 May 19, 2025
3b87eaf
Merge branch 'develop' of https://github.com/Team-Wable/WABLE-iOS int…
JinUng41 May 19, 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
312 changes: 296 additions & 16 deletions Wable-iOS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Wable-iOS/App/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ private extension SceneDelegate {

}
.store(in: cancelBag)

}

self.window?.rootViewController = TabBarController(shouldShowLoadingScreen: true)
Expand Down
18 changes: 18 additions & 0 deletions Wable-iOS/Data/Mapper/ErrorMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,21 @@ extension Publisher where Failure == NetworkError {
.eraseToAnyPublisher()
}
}

enum ErrorMapper {
static func map(_ error: Error) -> WableError {
WableLogger.log(error.localizedDescription, for: .network)
WableLogger.log("\(error)", for: .network)

if let networkError = error as? NetworkError {
switch networkError {
case .statusError(_, let message):
return WableError(rawValue: message) ?? .networkError
default:
return .unknownError
}
}

return .unknownError
}
}
3 changes: 2 additions & 1 deletion Wable-iOS/Data/Mapper/ProfileMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ extension ProfileMapper {
dateFormatter.timeZone = TimeZone(abbreviation: "KST")

let createdDate = dateFormatter.date(from: response.joinDate)
let socialPlatform = SocialPlatform(rawValue: response.socialPlatform)
let splitKeyword = response.socialPlatform.split(separator: " ").map { "\($0)" }.first
let socialPlatform = SocialPlatform(rawValue: splitKeyword ?? response.socialPlatform)

return AccountInfo(
memberID: response.memberID,
Expand Down
14 changes: 7 additions & 7 deletions Wable-iOS/Data/RepositoryImpl/ProfileRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ extension ProfileRepositoryImpl: ProfileRepository {
}
}

func fetchUserInfo() -> AnyPublisher<AccountInfo, WableError> {
return provider.request(
.fetchUserInfo,
for: DTO.Response.FetchAccountInfo.self
)
.map(ProfileMapper.toDomain)
.mapWableError()
func fetchAccountInfo() async throws -> AccountInfo {
do {
let response = try await provider.request(.fetchUserInfo, for: DTO.Response.FetchAccountInfo.self)
return ProfileMapper.toDomain(response)
} catch {
throw ErrorMapper.map(error)
}
}

func fetchUserProfile(memberID: Int) -> AnyPublisher<UserProfile, WableError> {
Expand Down
2 changes: 1 addition & 1 deletion Wable-iOS/Domain/Entity/UserProfile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

// MARK: - 유저 프로필

struct UserProfile {
struct UserProfile: Hashable {
let user: User
let introduction: String
let ghostCount: Int
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AccountDeleteReason.swift
// WithdrawalReason.swift
// Wable-iOS
//
// Created by 김진웅 on 2/16/25.
Expand All @@ -9,7 +9,7 @@ import Foundation

// MARK: - 계정 삭제 이유

enum AccountDeleteReason: String {
enum WithdrawalReason: String, CaseIterable {
case inappropriateContent = "온화하지 못한 내용이 많이 보여요."
case noDesiredContent = "원하는 콘텐츠가 없어요."
case missingCommunityFeatures = "필요한 커뮤니티 기능이 없어요."
Expand Down
3 changes: 1 addition & 2 deletions Wable-iOS/Domain/RepositoryInterface/ProfileRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
// Created by YOUJIM on 2/18/25.
//


import Combine
import Foundation
import UIKit

protocol ProfileRepository {
func fetchUserInfo() -> AnyPublisher<AccountInfo, WableError>
func fetchAccountInfo() async throws -> AccountInfo
func fetchUserProfile(memberID: Int) -> AnyPublisher<UserProfile, WableError>
func fetchFCMToken() -> String?
func updateFCMToken(token: String)
Expand Down
26 changes: 0 additions & 26 deletions Wable-iOS/Domain/UseCase/Home/FetchUserContentListUseCase.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// CreateUserProfileUseCase.swift
// UserProfileUseCase.swift
// Wable-iOS
//
// Created by YOUJIM on 3/6/25.
Expand All @@ -9,16 +9,16 @@
import Combine
import UIKit

final class CreateUserProfileUseCase {
final class UserProfileUseCase {
let repository: ProfileRepository

init(repository: ProfileRepository) {
self.repository = repository
}
}

extension CreateUserProfileUseCase {
func execute(profile: UserProfile, isPushAlarmAllowed: Bool, isAlarmAllowed: Bool, image: UIImage? = nil, defaultProfileType: String? = nil) -> AnyPublisher<Void, WableError> {
extension UserProfileUseCase {
func execute(profile: UserProfile? = nil, isPushAlarmAllowed: Bool? = nil, isAlarmAllowed: Bool? = nil, image: UIImage? = nil, defaultProfileType: String? = nil) -> AnyPublisher<Void, WableError> {
return repository.updateUserProfile(
profile: profile,
isPushAlarmAllowed: isPushAlarmAllowed,
Expand All @@ -28,4 +28,8 @@ extension CreateUserProfileUseCase {
defaultProfileType: defaultProfileType
)
}

func execute(userID: Int) -> AnyPublisher<UserProfile, WableError> {
return repository.fetchUserProfile(memberID: userID)
}
}
25 changes: 25 additions & 0 deletions Wable-iOS/Domain/UseCase/Profile/FetchAccountInfoUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// FetchAccountInfoUseCase.swift
// Wable-iOS
//
// Created by 김진웅 on 5/14/25.
//

import Combine
import Foundation

protocol FetchAccountInfoUseCase {
func execute() async throws -> AccountInfo
}

final class FetchAccountInfoUseCaseImpl: FetchAccountInfoUseCase {
private let repository: ProfileRepository

init(repository: ProfileRepository) {
self.repository = repository
}

func execute() async throws -> AccountInfo {
return try await repository.fetchAccountInfo()
}
}
29 changes: 29 additions & 0 deletions Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FetchUserCommentListUseCase.swift
// Wable-iOS
//
// Created by 김진웅 on 5/14/25.
//

import Combine
import Foundation

protocol FetchUserCommentListUseCase {
func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>
}

final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
private let repository: CommentRepository

init(repository: CommentRepository) {
self.repository = repository
}

func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError> {
guard userID > .zero else {
return .fail(.notFoundMember)
}

return repository.fetchUserCommentList(memberID: userID, cursor: commentID)
}
Comment on lines +22 to +28
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Consider using async/await instead of Combine.

According to the PR objectives, this PR aims to introduce async/await syntax. Consider updating this implementation to use async/await instead of Combine publishers for better code readability and alignment with the PR goals.

protocol FetchUserCommentListUseCase {
-    func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>
+    func execute(for userID: Int, last commentID: Int) async throws -> [UserComment]
}

final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
    private let repository: CommentRepository
    
    init(repository: CommentRepository) {
        self.repository = repository
    }
    
-    func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError> {
+    func execute(for userID: Int, last commentID: Int) async throws -> [UserComment] {
        guard userID > .zero else {
-            return .fail(.notFoundMember)
+            throw WableError.notFoundMember
        }

-        return repository.fetchUserCommentList(memberID: userID, cursor: commentID)
+        return try await repository.fetchUserCommentList(memberID: userID, cursor: commentID)
    }
}

🏁 Script executed:

#!/bin/bash
# Check if the repository layer supports async/await
rg "fetchUserCommentList.*async" --type swift

Length of output: 47


Adopt async/await across use-case and repository

To align with this PR’s async/await goal, update both the use-case and its repository dependency:

• In FetchUserCommentListUseCase (protocol + implementation), change

func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>

to

func execute(for userID: Int, last commentID: Int) async throws -> [UserComment]

and throw on userID <= 0, then call

try await repository.fetchUserCommentList(memberID: userID, cursor: commentID)

• In CommentRepository (and its conforming types), change

func fetchUserCommentList(memberID: Int, cursor: Int) -> AnyPublisher<[UserComment], WableError>

to

func fetchUserCommentList(memberID: Int, cursor: Int) async throws -> [UserComment]

and update all implementations accordingly.

// Protocol
-protocol FetchUserCommentListUseCase {
-    func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError>
-}
+protocol FetchUserCommentListUseCase {
+    func execute(for userID: Int, last commentID: Int) async throws -> [UserComment]
+}

// Implementation
-final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
+final class FetchUserCommentListUseCaseImpl: FetchUserCommentListUseCase {
     private let repository: CommentRepository
     
     init(repository: CommentRepository) {
         self.repository = repository
     }
     
-    func execute(for userID: Int, last commentID: Int) -> AnyPublisher<[UserComment], WableError> {
+    func execute(for userID: Int, last commentID: Int) async throws -> [UserComment] {
         guard userID > .zero else {
-            return .fail(.notFoundMember)
+            throw WableError.notFoundMember
         }
-
-        return repository.fetchUserCommentList(memberID: userID, cursor: commentID)
+        return try await repository.fetchUserCommentList(memberID: userID, cursor: commentID)
     }
 }
// In CommentRepository.swift
-protocol CommentRepository {
-    func fetchUserCommentList(memberID: Int, cursor: Int) -> AnyPublisher<[UserComment], WableError>
-    // …
-}
+protocol CommentRepository {
+    func fetchUserCommentList(memberID: Int, cursor: Int) async throws -> [UserComment]
+    // …
+}
🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift around
lines 22 to 28, refactor the execute function to use async/await instead of
Combine. Change the function signature to async throws returning [UserComment],
throw an error if userID is less than or equal to zero, and call the
repository's fetchUserCommentList method using try await. Also update the
CommentRepository protocol and its implementations to use async throws for
fetchUserCommentList to maintain consistency.

}
29 changes: 29 additions & 0 deletions Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FetchUserContentUseCase.swift
// Wable-iOS
//
// Created by 김진웅 on 5/14/25.
//

import Combine
import Foundation

protocol FetchUserContentListUseCase {
func execute(for userID: Int, last contentID: Int) -> AnyPublisher<[UserContent], WableError>
}

final class FetchUserContentUseCaseImpl: FetchUserContentListUseCase {
private let repository: ContentRepository

init(repository: ContentRepository) {
self.repository = repository
}

func execute(for userID: Int, last contentID: Int) -> AnyPublisher<[UserContent], WableError> {
if userID < .zero {
return .fail(.notFoundMember)
}

return repository.fetchUserContentList(memberID: userID, cursor: contentID)
}
}
31 changes: 31 additions & 0 deletions Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// FetchUserProfileUseCase.swift
// Wable-iOS
//
// Created by 김진웅 on 5/14/25.
//

import Combine
import Foundation

protocol FetchUserProfileUseCase {
func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError>
}

final class FetchUserProfileUseCaseImpl: FetchUserProfileUseCase {
private let repository: ProfileRepository

init(repository: ProfileRepository) {
self.repository = repository
}

func execute(userID: Int) -> AnyPublisher<UserProfile?, WableError> {
guard userID > .zero else {
return .fail(.notFoundMember)
}

return repository.fetchUserProfile(memberID: userID)
.map { $0 }
.eraseToAnyPublisher()
}
}
29 changes: 29 additions & 0 deletions Wable-iOS/Domain/UseCase/Profile/RemoveUserSessionUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// RemoveUserSessionUseCase.swift
// Wable-iOS
//
// Created by 김진웅 on 5/14/25.
//

import Foundation

protocol RemoveUserSessionUseCase {
func removeUserSession()
}

final class RemoveUserSessionUseCaseImpl: RemoveUserSessionUseCase {
private let repository: UserSessionRepository

init(repository: UserSessionRepository) {
self.repository = repository
}

func removeUserSession() {
guard let userID = repository.fetchActiveUserID() else {
WableLogger.log("유저 아이디를 찾을 수 없음.", for: .debug)
return
}

repository.removeUserSession(forUserID: userID)
}
}
27 changes: 27 additions & 0 deletions Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// WithdrawUseCase.swift
// Wable-iOS
//
// Created by 김진웅 on 5/14/25.
//

import Combine
import Foundation

protocol WithdrawUseCase {
func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError>
}
Comment on lines +11 to +13
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using async/await for consistency.

This use case uses Combine publishers while FetchAccountInfoUseCase uses async/await. According to the PR objectives, you're moving toward async/await for better readability. Consider making this consistent.

protocol WithdrawUseCase {
-    func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError>
+    func execute(reasons: [WithdrawalReason]) async throws -> Bool
}
📝 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
protocol WithdrawUseCase {
func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError>
}
protocol WithdrawUseCase {
func execute(reasons: [WithdrawalReason]) async throws -> Bool
}
🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift around lines 11 to 13,
the WithdrawUseCase protocol currently uses Combine's AnyPublisher for its
execute method, but to maintain consistency with FetchAccountInfoUseCase and
improve readability, refactor the execute method to use async/await by changing
its signature to an async function that returns a Bool and throws WableError.


final class WithdrawUseCaseImpl: WithdrawUseCase {
private let repository: AccountRepository

init(repository: AccountRepository) {
self.repository = repository
}

func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError> {
return repository.deleteAccount(reason: reasons.map { $0.rawValue })
.map { _ in true }
.eraseToAnyPublisher()
}
Comment on lines +22 to +26
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement async method consistently with PR objectives.

The implementation should follow the async/await pattern for consistency with other use cases and to align with the PR's focus on improving code readability.

-func execute(reasons: [WithdrawalReason]) -> AnyPublisher<Bool, WableError> {
-    return repository.deleteAccount(reason: reasons.map { $0.rawValue })
-        .map { _ in true }
-        .eraseToAnyPublisher()
+async func execute(reasons: [WithdrawalReason]) async throws -> Bool {
+    try await repository.deleteAccount(reason: reasons.map { $0.rawValue })
+    return true
}

Note: This assumes that the AccountRepository.deleteAccount method would also be updated to use async/await. If that method hasn't been updated yet, you might need to create an adapter pattern or update both simultaneously.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Wable-iOS/Domain/UseCase/Profile/WithdrawUseCase.swift around lines 22 to 26,
the execute method currently returns a Combine publisher but should be
refactored to use async/await for consistency with the PR objectives. Change the
method signature to async and have it call an async version of
repository.deleteAccount, awaiting its result and returning a Bool directly.
Ensure that repository.deleteAccount is also updated to support async/await or
create an adapter to bridge between Combine and async/await if needed.

}
27 changes: 27 additions & 0 deletions Wable-iOS/Infra/Network/APIProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,33 @@ final class APIProvider<Target: BaseTargetType>: MoyaProvider<Target> {
.eraseToAnyPublisher()
}

func request<D: Decodable>(
_ target: Target,
for type: D.Type
) async throws -> D {
let response = try await withCheckedThrowingContinuation { continuation in
self.request(target) { result in
switch result {
case .success(let moyaResponse):
continuation.resume(returning: moyaResponse)
case .failure(let error):
continuation.resume(throwing: NetworkError.unknown(error))
}
}
}

do {
let baseResponse = try jsonDecoder.decode(BaseResponse<D>.self, from: response.data)
return try validateResponse(baseResponse)
} catch let decodingError as DecodingError {
throw NetworkError.decodedError(decodingError)
} catch let error as NetworkError {
throw error
} catch {
throw NetworkError.unknown(error)
}
}
Comment on lines +73 to +98
Copy link
Member

Choose a reason for hiding this comment

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

👍


private func validateResponse<D>(_ baseResponse: BaseResponse<D>) throws -> D {
guard baseResponse.success else {
throw convertNetworkError(statusCode: baseResponse.status, message: baseResponse.message)
Expand Down
2 changes: 1 addition & 1 deletion Wable-iOS/Infra/Network/TargetType/CommentTargetType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension CommentTargetType: BaseTargetType {
var endPoint: String? {
switch self {
case .fetchUserCommentList(memberID: let memberID, _):
return "/v3/member/\(memberID)/member-comments"
return "/v3/member/\(memberID)/comments"
Copy link
Member

Choose a reason for hiding this comment

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

ㅎㅎ ... 고생하셨씁니다

case .fetchContentCommentList(contentID: let contentID, _):
return "/v3/content/\(contentID)/comments"
case .deleteComment(commentID: let commentID):
Expand Down
Loading