Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
6bc1b19
[추가] 마이 화면 API 및 클린 아키텍처 파일 추가
hyeonsik971029 Nov 6, 2025
3c3b5c0
[변경] 마이 화면에 사용될 API 및 클린 아키텍처 관련 파일 변경
hyeonsik971029 Nov 6, 2025
68cffdb
[변경] 마이(프로필, 프로필 변경, 팔로우) API 및 클린 아키텍처 파일 변경
hyeonsik971029 Nov 8, 2025
14f311b
[변경] 개발에 필요한 유틸 파일 변경
hyeonsik971029 Nov 8, 2025
7dc20c3
[변경] 디자인 시스템 변경
hyeonsik971029 Nov 8, 2025
cf7efd1
[수정] 오타 수정
hyeonsik971029 Nov 8, 2025
3474312
[변경] 프로필 탭 화면 숨 버전2 개선 적용
hyeonsik971029 Nov 8, 2025
69ca1dd
[변경] 숨 버전2를 위한 프로필 변경 화면
hyeonsik971029 Nov 8, 2025
32014b4
[변경] 숨 버전2를 위한 팔로우 화면
hyeonsik971029 Nov 8, 2025
c8d0d7f
[변경] 상세 화면에서 프로필 화면 전환 추가
hyeonsik971029 Nov 8, 2025
9968efe
[삭제] 구버전 조회 API 삭제
hyeonsik971029 Nov 9, 2025
fb92f2a
[추가] 설정 관련 API 및 클린 아키텍처 파일 추가
hyeonsik971029 Nov 9, 2025
fa9043d
[추가] 설정 관련 파일 추가
hyeonsik971029 Nov 9, 2025
af5bad5
[수정] 오타 수정
hyeonsik971029 Nov 9, 2025
4d5b838
[변경] 프로필 변경
hyeonsik971029 Nov 9, 2025
4c7e552
[변경] 팔로우 화면 변경
hyeonsik971029 Nov 9, 2025
d9b5dff
[변경] 숨 버전2를 위한 설정 개선
hyeonsik971029 Nov 9, 2025
5c9d697
[버전] Develop 1.18.0(1018000) 버전 업데이트
hyeonsik971029 Nov 9, 2025
ff3fae1
[변경] 내 프로필일 경우에만 제스처 델리게이트 설정
hyeonsik971029 Nov 12, 2025
c55d478
[추가] 알림 화면 태그 알림 포맷 추가
hyeonsik971029 Nov 12, 2025
42adba6
[수정] 문구 수정 '답카드' -> '댓글카드'
hyeonsik971029 Nov 12, 2025
029e651
[추가] 셀 재사용 시 기존 데이터 초기화
hyeonsik971029 Nov 12, 2025
4ac4009
[수정] 프로필 업데이트 화면에서 기본 이미지 변경 기능 추가
hyeonsik971029 Nov 12, 2025
002890f
[추가] 네트워크 불안정 토스트 메시지 및 싱글톤 클래스 추가
hyeonsik971029 Nov 12, 2025
1a9710d
[변경] 팔로우 알림일 때, 상대방 프로필로 전환
hyeonsik971029 Nov 12, 2025
705fe16
[수정] 삭제된 카드일 때, 팝업 표시
hyeonsik971029 Nov 12, 2025
08bb377
[추가] 홈 공지 페이지 뷰 오토 스크롤 추가
hyeonsik971029 Nov 12, 2025
d23ba89
[추가] 카드 작성 중 이용 안내 제한 케이스 추가
hyeonsik971029 Nov 13, 2025
d421b46
[추가] 네트워크 토스트 메시지 패딩 추가
hyeonsik971029 Nov 13, 2025
a137848
[추가] 설정 화면 재가입 날짜 조회 API 및 클린 아키텍처 파일 추가
hyeonsik971029 Nov 13, 2025
246eed2
[변경] 회원탈퇴 팝업 표시 시 데이터 변경
hyeonsik971029 Nov 13, 2025
ee3b9a8
[추가] API 호출 중 네트워크 오류 핸들링
hyeonsik971029 Nov 13, 2025
44464a8
[추가] 설정 화면 상태 초기화 코드 추가
hyeonsik971029 Nov 13, 2025
d01f489
[추가] 오토 스크롤 시 뷰 로드 시점 추가
hyeonsik971029 Nov 13, 2025
16a77a8
[변경] 서버에서 받은 이미지 원본 비율 유지
hyeonsik971029 Nov 13, 2025
4735e3d
[수정] 메모리 누수 해결
hyeonsik971029 Nov 13, 2025
f8c863e
[수정] 프로필 업데이트 화면에서 사용자 이미지일 때, 이미지 초기화 추가
hyeonsik971029 Nov 13, 2025
13a063d
[버전] Develop 1.18.1(1018010) 버전 업데이트
hyeonsik971029 Nov 13, 2025
7529e7a
[수정] 홈 화면 시간 표시 수정
hyeonsik971029 Nov 13, 2025
ac79f44
[변경] 차단 및 팔로우 상태 pulse 사용 및 UI 업데이트 로직 변경
hyeonsik971029 Nov 13, 2025
a3be13a
[추가] 현재 쓰이지 않는 기능 주석 처리
hyeonsik971029 Nov 13, 2025
963f46e
[추가] Dialog 표시할 때, 중복 제거
hyeonsik971029 Nov 13, 2025
4dd6d0b
[변경] 네트워크 유실 감지 로직 변경
hyeonsik971029 Nov 13, 2025
58a9220
[버전] Develop 1.18.2(1018020) 버전 업데이트
hyeonsik971029 Nov 13, 2025
f151302
[변경] 프로필 업데이트 초기화 시 프로필 이미지 직접 받는 것으로 변경
hyeonsik971029 Nov 16, 2025
3d3eddd
[수정] 프로필 업데이트 방식 변경
hyeonsik971029 Nov 16, 2025
ef16dc8
[변경] 토큰 재인증 시 성공 여부에 따라 바로 반환하는 것으로 변경
hyeonsik971029 Nov 16, 2025
be19c36
[추가] Firebase Crashlytics 추가
hyeonsik971029 Nov 16, 2025
820c3e0
[수정] SOMDialog 수정
hyeonsik971029 Nov 16, 2025
b241c7c
[수정] header 탭 아이템이 변경될 때, 선택된 인덱스 사용
hyeonsik971029 Nov 16, 2025
92dd109
[수정] 닉네임 텍스트 필드 삭제 버튼 수정
hyeonsik971029 Nov 16, 2025
4699b4b
[수정] 카드추가 화면 중복 push 수정
hyeonsik971029 Nov 16, 2025
56eec8e
[수정] 팔로우 화면 팔로잉 취소 시 다이얼로그 표시
hyeonsik971029 Nov 16, 2025
384eae0
[수정] 부적절한 이미지 사용 시 기본 이미지로 변경 시점 수정
hyeonsik971029 Nov 16, 2025
8375f02
[수정] 공백 혹은 줄바꿈만 있을 경우 카드 작성 버튼 비활성화
hyeonsik971029 Nov 16, 2025
f0e8e1a
[버전] Develop 1.18.3(1018030) 버전 업데이트
hyeonsik971029 Nov 16, 2025
54ffbff
[수정] 공지 조회 API 변경에 따른 수정
hyeonsik971029 Nov 16, 2025
35714fc
[수정] 텍스트 입력 중 clearButton 숨김 여부 수정
hyeonsik971029 Nov 16, 2025
bcd4643
[수정] 프로필 업데이트 화면에서 기본 이미지 변경 기능 추가
hyeonsik971029 Nov 16, 2025
2546bca
[추가] NetworkManager 온보딩 화면 보낸 후 반환 추가
hyeonsik971029 Nov 16, 2025
98558a7
[추가] 글자수 제한 시 한글 조합 고려해서 적용
hyeonsik971029 Nov 16, 2025
29c17d4
[수정] 프로필 업데이트 버튼 활성화 로직 수정
hyeonsik971029 Nov 16, 2025
17714b3
[수정] 하단 네비게이션 바 선택되지 않은 이미지 색상 수정
hyeonsik971029 Nov 16, 2025
2ee2820
[수정] 에러 핸들링 수정
hyeonsik971029 Nov 16, 2025
5f0929a
[버전] Develop 1.18.4(1018040) 버전 업데이트
hyeonsik971029 Nov 16, 2025
6399f1d
[변경] reloadData 시에 애니메이션 제거
hyeonsik971029 Nov 16, 2025
1e27a29
[변경] 온보딩 화면 밴 사용자 판단 로직 변경
hyeonsik971029 Nov 16, 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
398 changes: 284 additions & 114 deletions SOOUM/SOOUM.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion SOOUM/SOOUM/Base/BaseNavigationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class BaseNavigationViewController: BaseViewController {
$0.isHidden = true
}

private(set) var navigationPopWithBottomBarHidden: Bool = false
private(set) var navigationPopWithBottomBarHidden: Bool = true
private(set) var navigationPopGestureEnabled: Bool = true
private(set) var navigationBarHeight: CGFloat = SOMNavigationBar.height

Expand Down
28 changes: 26 additions & 2 deletions SOOUM/SOOUM/Base/BaseViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import UIKit

import Network

import RxKeyboard
import RxSwift

Expand All @@ -15,15 +17,24 @@ import Then

import Lottie


class BaseViewController: UIViewController {

enum Text {
static let bottomToastEntryName: String = "bottomToastEntryName"
static let instabilityNetworkToastTitle: String = "네트워크 연결이 원활하지 않습니다. 네트워크 확인 후 재접속해주세요"
}

var disposeBag = DisposeBag()

private let monitor = NWPathMonitor()

private let instabilityNetworkToastView = SOMBottomToastView(title: Text.instabilityNetworkToastTitle, actions: nil)

let activityIndicatorView = SOMActivityIndicatorView()
let loadingIndicatorView = SOMLoadingIndicatorView()

private(set) var isEndEditingWhenWillDisappear: Bool = true
private(set) var bottomToastMessageOffset: CGFloat = 88 + 8

override var hidesBottomBarWhenPushed: Bool {
didSet {
Expand All @@ -47,7 +58,7 @@ class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

self.view.backgroundColor = .white
self.view.backgroundColor = .som.v2.white
self.setupConstraints()

self.activityIndicatorView.color = .black
Expand All @@ -74,6 +85,19 @@ class BaseViewController: UIViewController {
object.updatedKeyboard(withoutBottomSafeInset: withoutBottomSafeInset)
}
.disposed(by: self.disposeBag)

SimpleReachability.shared.isConnected
.skip(1)
.filter { $0 == false }
.observe(on: MainScheduler.asyncInstance)
.subscribe(with: self) { object, _ in
guard object.isViewLoaded, object.view.window != nil else { return }

var wrapper: SwiftEntryKitViewWrapper = object.instabilityNetworkToastView.sek
wrapper.entryName = Text.bottomToastEntryName
wrapper.showBottomToast(verticalOffset: object.bottomToastMessageOffset, displayDuration: 4)
}
.disposed(by: self.disposeBag)
}

/// Set auto layouts
Expand Down
26 changes: 17 additions & 9 deletions SOOUM/SOOUM/Data/Managers/AuthManager/AuthManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ extension AuthManager: AuthManagerDelegate {
provider.networkManager.registerFCMToken(from: #function)
return .just(true)
}
.catchAndReturn(false)
} else {
return .just(false)
}
Expand Down Expand Up @@ -193,6 +194,7 @@ extension AuthManager: AuthManagerDelegate {
provider.networkManager.registerFCMToken(from: #function)
return .just(true)
}
.catchAndReturn(false)
} else {
return .just(false)
}
Expand Down Expand Up @@ -265,15 +267,21 @@ extension AuthManager: AuthManagerDelegate {
},
onError: { object, error in

let errorCode = (error as NSError).code
if case 403 = errorCode {

object.certification()
.subscribe(onNext: { isRegistered in
object.excutePendingResults(isRegistered ? .success : .failure(error))
})
.disposed(by: object.disposeBag)
}
// TODO: 임시, 리프레쉬 토큰 만료 에러코드가 정의되지 않음
// let errorCode = (error as NSError).code
// if case 403 = errorCode {
//
// object.certification()
// .subscribe(onNext: { isRegistered in
// object.excutePendingResults(isRegistered ? .success : .failure(error))
// })
// .disposed(by: object.disposeBag)
// }
object.certification()
.subscribe(onNext: { isRegistered in
object.excutePendingResults(isRegistered ? .success : .failure(error))
})
.disposed(by: object.disposeBag)
}
)
.disposed(by: self.disposeBag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,31 @@
// Created by 오현식 on 10/27/24.
//

import Foundation

import Alamofire


class ErrorInterceptor: RequestInterceptor {

enum Text {
static let networkErrorDialogTitle: String = "네트워크 상태가 불안정해요"
static let networkErrorDialogMessage: String = "네트워크 연결상태를 확인 후 다시 시도해 주세요."
static let confirmActionTitle: String = "확인"

static let unknownErrorDialogTitle: String = "일시적인 오류가 발생했어요"
static let unknownErrorDialogMessage: String = "같은 문제가 반복된다면 ‘문의하기'를 눌러 숨 팀에 알려주세요."
static let closeActionButtonTitle: String = "닫기"
static let inquiryActionTitle: String = "문의하기"

static let adminMailStrUrl: String = "[email protected]"
static let identificationInfo: String = "식별 정보: "
static let inquiryMailTitle: String = "[문의하기]"
static let inquiryMailGuideMessage: String = """
\n
문의 내용: 식별 정보 삭제에 주의하여 주시고, 이곳에 자유롭게 문의하실 내용을 적어주세요.
단, 본 양식에 비방, 욕설, 허위 사실 유포 등의 부적절한 내용이 포함될 경우,
관련 법령에 따라 민·형사상 법적 조치가 이루어질 수 있음을 알려드립니다.
"""
}

private let lock = NSLock()

private let retryLimit: Int = 1
Expand All @@ -25,29 +43,150 @@ class ErrorInterceptor: RequestInterceptor {
func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) {
self.lock.lock(); defer { self.lock.unlock() }

guard let response = request.task?.response as? HTTPURLResponse,
response.statusCode == 401
else {
completion(.doNotRetryWithError(error))
return
/// API 호출 중 네트워크 오류 발생
if let afError = error.asAFError,
case let .sessionTaskFailed(underlyingError) = afError,
let urlError = underlyingError as? URLError {

let networkErrors = [
URLError.timedOut,
URLError.notConnectedToInternet,
URLError.networkConnectionLost,
URLError.cannotConnectToHost
]
if networkErrors.contains(urlError.code) {
self.showNetworkErrorDialog()
completion(.doNotRetry)
return
}
}

// 재인증 과정은 1번만 진행한다.
guard request.retryCount < retryLimit else {
guard let response = request.task?.response as? HTTPURLResponse else {
completion(.doNotRetryWithError(error))
return
}

let token = self.provider.authManager.authInfo.token
self.provider.authManager.reAuthenticate(token) { result in
switch response.statusCode {
/// AccessToken 재인증
case 401:
// 재인증 과정은 1번만 진행한다.
guard request.retryCount < self.retryLimit else {
let retryError = NSError(
domain: "SOOUM",
code: -99,
userInfo: [
NSLocalizedDescriptionKey: "Retry error: ReAuthenticate process is performed only once."
]
)
completion(.doNotRetryWithError(retryError))
return
}

switch result {
case .success:
completion(.retry)
case let .failure(error):
Log.error("ReAuthenticate failed. \(error.localizedDescription)")
completion(.doNotRetry)
let token = self.provider.authManager.authInfo.token
self.provider.authManager.reAuthenticate(token) { result in

switch result {
case .success:
completion(.retry)
case let .failure(error):
Log.error("ReAuthenticate failed. \(error.localizedDescription)")
completion(.doNotRetry)
}
}
return
case 403:
self.goToOnboarding()
completion(.doNotRetry)
return
case 500:
self.showUnknownErrorDialog()
completion(.doNotRetry)
return
default:
break
}

completion(.doNotRetryWithError(error))
}


// MARK: Error handling

func goToOnboarding() {

self.provider.authManager.initializeAuthInfo()

DispatchQueue.main.async {
if let window: UIWindow = UIApplication.currentWindow,
let appDelegate = UIApplication.shared.delegate as? AppDelegate {

let onboardingViewController = OnboardingViewController()
onboardingViewController.reactor = OnboardingViewReactor(dependencies: appDelegate.appDIContainer)
window.rootViewController = UINavigationController(rootViewController: onboardingViewController)
}
}
}

func showNetworkErrorDialog() {

let confirmAction = SOMDialogAction(
title: Text.confirmActionTitle,
style: .primary,
action: {
UIApplication.topViewController?.dismiss(animated: true)
}
)

DispatchQueue.main.async {
SOMDialogViewController.show(
title: Text.networkErrorDialogTitle,
message: Text.networkErrorDialogMessage,
textAlignment: .left,
actions: [confirmAction]
)
}
}

func showUnknownErrorDialog() {

let closeAction = SOMDialogAction(
title: Text.closeActionButtonTitle,
style: .gray,
action: {
UIApplication.topViewController?.dismiss(animated: true)
}
)

let inquireAction = SOMDialogAction(
title: Text.inquiryActionTitle,
style: .primary,
action: {
UIApplication.topViewController?.dismiss(animated: true) {
let subject = Text.inquiryMailTitle.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let guideMessage = """
\(Text.identificationInfo)
\(self.provider.authManager.authInfo.token.refreshToken)\n
\(Text.inquiryMailGuideMessage)
"""
let body = guideMessage.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let mailToString = "mailto:\(Text.adminMailStrUrl)?subject=\(subject)&body=\(body)"

if let mailtoUrl = URL(string: mailToString),
UIApplication.shared.canOpenURL(mailtoUrl) {

UIApplication.shared.open(mailtoUrl, options: [:], completionHandler: nil)
}
}
}
)

DispatchQueue.main.async {
SOMDialogViewController.show(
title: Text.unknownErrorDialogTitle,
message: Text.unknownErrorDialogMessage,
textAlignment: .left,
actions: [closeAction, inquireAction]
)
}
}
}
15 changes: 11 additions & 4 deletions SOOUM/SOOUM/Data/Managers/NetworkManager/NetworkManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protocol NetworkManagerDelegate: AnyObject {
func upload(
_ data: Data,
to url: URLConvertible
) -> Observable<Result<Void, Error>>
) -> Observable<Result<Int, Error>>

func fetch<T: Decodable>(_ object: T.Type, request: BaseRequest) -> Observable<T>
func perform(_ request: BaseRequest) -> Observable<Int>
Expand Down Expand Up @@ -139,16 +139,23 @@ extension NetworkManager: NetworkManagerDelegate {
func upload(
_ data: Data,
to url: URLConvertible
) -> Observable<Result<Void, Error>> {
) -> Observable<Result<Int, Error>> {
return Observable.create { [weak self] observer -> Disposable in

let task = self?.session.upload(data, to: url, method: .put)
.validate(statusCode: 200..<500)
.response { response in
let statusCode = response.response?.statusCode ?? 0

switch response.result {
case .success:
observer.onNext(.success(()))
observer.onCompleted()
if let nsError = self?.setupError(with: statusCode) {
Log.error(nsError.localizedDescription)
observer.onError(nsError)
} else {
observer.onNext(.success(statusCode))
observer.onCompleted()
}
case let .failure(error):
Log.error("Network or response format error: \(error)")
observer.onError(error)
Expand Down
32 changes: 32 additions & 0 deletions SOOUM/SOOUM/Data/Models/Responses/BlockUsersInfoResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// BlockUsersInfoResponse.swift
// SOOUM
//
// Created by 오현식 on 11/9/25.
//

import Alamofire

struct BlockUsersInfoResponse {

let blockUsers: [BlockUserInfo]
}

extension BlockUsersInfoResponse: EmptyResponse {

static func emptyValue() -> BlockUsersInfoResponse {
BlockUsersInfoResponse(blockUsers: [])
}
}

extension BlockUsersInfoResponse: Decodable {

enum CodingKeys: String, CodingKey {
case blockUsers
}

init(from decoder: any Decoder) throws {
let singleContainer = try decoder.singleValueContainer()
self.blockUsers = try singleContainer.decode([BlockUserInfo].self)
}
}
Loading
Loading