Skip to content

Commit

Permalink
[Chore] #196 Conflicts 해결
Browse files Browse the repository at this point in the history
  • Loading branch information
EunsuSeo01 committed Oct 29, 2023
2 parents 19edc7f + ea472b9 commit 07e8074
Show file tree
Hide file tree
Showing 43 changed files with 1,130 additions and 529 deletions.
1 change: 1 addition & 0 deletions Milestone/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ disabled_rules:
- nesting
- cyclomatic_complexity
- mark
- large_tuple

opt_in_rules:
- empty_count
Expand Down
126 changes: 90 additions & 36 deletions Milestone/Milestone.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

98 changes: 62 additions & 36 deletions Milestone/Milestone/Core/API/APIInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import Alamofire
import RxSwift

class APIInterceptor: RequestInterceptor {
let disposeBag = DisposeBag()
var disposeBag = DisposeBag()
var retryDisposeBag = DisposeBag()

static var isRefreshing = false
static let retryObservable = PublishSubject<Void>()

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {

Expand All @@ -30,7 +34,6 @@ class APIInterceptor: RequestInterceptor {
return
} else {
guard let accessToken: String = try? KeychainManager.shared.retrieveItem(ofClass: .password, key: KeychainKeyList.accessToken.rawValue) else {
print("NIL")
return
}
var urlRequest = urlRequest
Expand All @@ -41,54 +44,72 @@ class APIInterceptor: RequestInterceptor {
}

func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {

guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
completion(.doNotRetryWithError(error))
return
}

if let url = request.response?.url {
if url.relativePath == "/auth/reissue" {
APIInterceptor.isRefreshing = false
completion(.doNotRetryWithError(APIError.http(status: 401)))
} else {
APIRefreshTask().requestRefreshToken()
.subscribe(onNext: {[unowned self] result in
switch result {
case .success(let response):
KeychainManager.shared.rx.saveItem(response.data.accessToken, itemClass: .password, key: KeychainKeyList.accessToken.rawValue)
.subscribe(onNext: {
print("accessToken save completed!")
})
.disposed(by: self.disposeBag)
KeychainManager.shared.rx.saveItem(response.data.refreshToken, itemClass: .password, key: KeychainKeyList.refreshToken.rawValue)
.subscribe(onNext: {
print("refreshToken save completed!")
})
.disposed(by: self.disposeBag)
print("SAVE COMPLETED!")
if APIInterceptor.isRefreshing {
APIInterceptor.retryObservable
.debug()
.subscribe(onNext: { [unowned self] in
completion(.retry)
case .failure:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let self = self else { return }
let window = UIApplication.shared.connectedScenes.compactMap { ($0 as? UIWindowScene)?.keyWindow }.last
let root = window?.rootViewController

let accessTokenObservable = KeychainManager.shared.rx
.deleteItem(ofClass: .password, key: KeychainKeyList.accessToken.rawValue)
let refreshTokenObservable = KeychainManager.shared.rx
.deleteItem(ofClass: .password, key: KeychainKeyList.refreshToken.rawValue)

Observable.combineLatest(accessTokenObservable, refreshTokenObservable)
self.retryDisposeBag = DisposeBag()
})
.disposed(by: retryDisposeBag)
APIInterceptor.isRefreshing = false
} else {
APIInterceptor.isRefreshing = true

APIRefreshTask().requestRefreshToken()
.subscribe(onNext: {[unowned self] result in
switch result {
case .success(let response):
KeychainManager.shared.rx.saveItem(response.data.accessToken, itemClass: .password, key: KeychainKeyList.accessToken.rawValue)
.subscribe(onCompleted: {
print("accessToken save completed!")
})
.disposed(by: self.disposeBag)
KeychainManager.shared.rx.saveItem(response.data.refreshToken, itemClass: .password, key: KeychainKeyList.refreshToken.rawValue)
.subscribe(onCompleted: {
AppCoordinator(window: window!).start()
print("refreshToken save completed!")
})
.disposed(by: self.disposeBag) }
completion(.doNotRetry)
}
})
.disposed(by: disposeBag)
.disposed(by: self.disposeBag)
APIInterceptor.isRefreshing = false
APIInterceptor.retryObservable.onNext(())
completion(.retry)
case .failure:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let self = self else { return }
let window = UIApplication.shared.connectedScenes.compactMap { ($0 as? UIWindowScene)?.keyWindow }.last
let root = window?.rootViewController

let accessTokenObservable = KeychainManager.shared.rx
.deleteItem(ofClass: .password, key: KeychainKeyList.accessToken.rawValue)
let refreshTokenObservable = KeychainManager.shared.rx
.deleteItem(ofClass: .password, key: KeychainKeyList.refreshToken.rawValue)

Observable.combineLatest(accessTokenObservable, refreshTokenObservable)
.subscribe(onCompleted: {
AppCoordinator(window: window!).start()
})
.disposed(by: self.disposeBag)
}
APIInterceptor.isRefreshing = false
APIInterceptor.retryObservable.onNext(())
completion(.doNotRetry)
}
})
.disposed(by: disposeBag)
}
}
}

}

func checkIsRegisterAPI(urlRequest: URLRequest) -> Bool {
Expand All @@ -106,6 +127,11 @@ class APIInterceptor: RequestInterceptor {
return false
}
}

deinit {
retryDisposeBag = DisposeBag()
disposeBag = DisposeBag()
}
}

class APIRefreshTask: ServicesUser {
Expand Down
6 changes: 3 additions & 3 deletions Milestone/Milestone/Core/API/APIRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ enum APIRouter: URLRequestConvertible {
switch self {
case .requestAllGoals(let lastGoalId, let goalStatus):
return [
K.Parameters.lastId: lastGoalId,
K.Parameters.lastId: lastGoalId ?? 0,
K.Parameters.goalStatus: goalStatus.rawValue
]
case .deleteGoal:
Expand All @@ -167,8 +167,8 @@ enum APIRouter: URLRequestConvertible {
return nil
case .editGoal(_, let goal):
return [
K.Parameters.goalId: goal.identity,
K.Parameters.title: goal.title,
K.Parameters.goalId: goal.identity ?? 0,
K.Parameters.title: goal.title ?? "",
K.Parameters.startDate: goal.startDate,
K.Parameters.endDate: goal.endDate,
K.Parameters.reminderEnabled: goal.reminderEnabled
Expand Down
1 change: 1 addition & 0 deletions Milestone/Milestone/Core/API/APIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ import RxSwift

protocol APIService {
func request<T: Codable> (_ request: APIRouter) -> Observable<Result<T, APIError>>
func requestSingle<T: Codable> (_ request: APIRouter) -> Single<T>
}
29 changes: 29 additions & 0 deletions Milestone/Milestone/Core/API/APISession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,33 @@ struct APISession: APIService {
}
}
}

/// Single Trait으로 데이터 요청
func requestSingle<T: Codable>(_ request: APIRouter) -> Single<T> {
return Single<T>.create { observer -> Disposable in
let request = API.session.request(request, interceptor: APIInterceptor()).responseDecodable { (response: DataResponse<T, AFError>) in
guard let statusCode = response.response?.statusCode else {
observer(.failure(APIError.unknown))
return
}

guard (200 ... 399).contains(statusCode) else {
observer(.failure(APIError.http(status: statusCode)))
return
}

guard let decoded = response.data?.decode(T.self) else {
observer(.failure(APIError.decode))
return
}

observer(.success(decoded))
return
}

return Disposables.create {
request.cancel()
}
}
}
}
24 changes: 20 additions & 4 deletions Milestone/Milestone/Core/Services/ServicesGoalList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ protocol ServicesGoalList: Service {
func postReview(higherLevelGoalId: Int, retrospect: Retrospect) -> Observable<Result<BaseModel<Int>, APIError>>

func requestRetrospect(goalId: Int) -> Observable<Result<BaseModel<Retrospect>, APIError>>

func requestEnabledRetrospectCount() -> Observable<Result<BaseModel<RetrospectCount>, APIError>>

func requestAllGoals(lastGoalId: Int, goalStatusParameter: GoalStatusParameter) -> Observable<Result<BaseModel<GoalResponse>, APIError>>

Expand All @@ -30,6 +28,11 @@ protocol ServicesGoalList: Service {
func requestRestoreUpperGoal(id: Int, reqBody: Goal) -> Observable<Result<EmptyDataModel, APIError>>

func requestRecommendGoal() -> Observable<Result<BaseModel<[UpperGoal]>, APIError>>

// MARK: - Single Trait 리팩토링 함수
func requestEnabledRetrospectCount() -> Single<BaseModel<RetrospectCount>>

func requestAllGoalsWithSignle(lastGoalId: Int, goalStatusParameter: GoalStatusParameter) -> Single<BaseModel<GoalResponse>>
}

extension ServicesGoalList {
Expand All @@ -45,8 +48,8 @@ extension ServicesGoalList {
return apiSession.request(.requestRetrospect(higherLevelGoalId: goalId))
}

func requestEnabledRetrospectCount() -> Observable<Result<BaseModel<RetrospectCount>, APIError>> {
return apiSession.request(.requestEnabledRetrospectCount)
func requestEnabledRetrospectCount() -> Single<BaseModel<RetrospectCount>> {
return apiSession.requestSingle(.requestEnabledRetrospectCount)
}

func requestGoalCountByStatus() -> Observable<Result<BaseModel<UpperGoalCount>, APIError>> {
Expand Down Expand Up @@ -76,4 +79,17 @@ extension ServicesGoalList {
func requestRecommendGoal() -> Observable<Result<BaseModel<[UpperGoal]>, APIError>> {
return apiSession.request(.requestRecommendGoal)
}

func requestAllGoalsWithSignle(lastGoalId: Int, goalStatusParameter: GoalStatusParameter) -> Single<BaseModel<GoalResponse>> {
return apiSession.requestSingle(.requestAllGoals(lastGoalId: lastGoalId, goalStatus: goalStatusParameter))
}

func postRetrospectSingle(higherLevelGoalId: Int, retrospect: Retrospect) -> Single<BaseModel<Int>> {
return apiSession.requestSingle(.postRetrospect(higherLevelGoalId: higherLevelGoalId, retrospect: retrospect))
}

// MARK: - Single Trait
func requestRetrospectWithSingle(goalId: Int) -> Single<BaseModel<Retrospect>> {
return apiSession.requestSingle(.requestRetrospect(higherLevelGoalId: goalId))
}
}
8 changes: 8 additions & 0 deletions Milestone/Milestone/Global/Base/ViewModelBindableType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ extension ViewModelBindableType where Self: UIViewController {
// bind가 필요없는 경우 ViewModelBindableType 채택 시 굳이 이 함수를 작성하지 않아도 돌아가게끔 하려고
}
}

protocol ViewModelType {
associatedtype Input
associatedtype Output

func transform(input: Input) -> Output
}

Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class IndexView: UIView {
$0.attributedText = NSAttributedString(string: $0.text, attributes: attributes)
}

private let textCountLabel = UILabel()
let textCountLabel = UILabel()
.then {
$0.font = UIFont.pretendard(.regular, ofSize: 10)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import UIKit
// MARK: - content size에 맞게 높이를 자동 조절하는 테이블뷰

final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}

override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric,
height: contentSize.height + adjustedContentInset.top)
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height + adjustedContentInset.top)
}
}
79 changes: 79 additions & 0 deletions Milestone/Milestone/Global/Component/Shared/NetworkFailView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// NetworkFailView.swift
// Milestone
//
// Created by 서은수 on 2023/09/27.
//

import UIKit

import RxCocoa
import RxSwift
import SnapKit
import Then

// MARK: - 네트워크 접속 실패 뷰

class NetworkFailView: UIView {

// MARK: - Subviews

var failImageView = UIImageView()
.then {
$0.image = ImageLiteral.imgNetworkFail
}
var guideLabel = UILabel()
.then {
$0.numberOfLines = 0
$0.text = "네트워크에 접속할 수 없습니다.\n네트워크 연결 상태를 확인해주세요!"
$0.textAlignment = .center
$0.font = .pretendard(.semibold, ofSize: 18)
$0.textColor = .gray02
}
lazy var retryButton = UIButton()
.then {
$0.setTitle("재시도", for: .normal)
$0.titleLabel?.font = .pretendard(.semibold, ofSize: 16)
$0.layer.masksToBounds = true
$0.layer.cornerRadius = 20
$0.backgroundColor = .primary
}

// MARK: - Initialization

override init(frame: CGRect) {
super.init(frame: frame)

render()
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Functions

private func render() {
addSubViews([failImageView, guideLabel, retryButton])

self.snp.makeConstraints { make in
make.width.equalTo(390)
make.height.equalTo(401)
}
failImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.width.height.equalTo(250)
}
guideLabel.snp.makeConstraints { make in
make.top.equalTo(failImageView.snp.bottom).offset(24)
make.centerX.equalToSuperview()
}
retryButton.snp.makeConstraints { make in
make.top.equalTo(guideLabel.snp.bottom).offset(40)
make.centerX.bottom.equalToSuperview()
make.width.equalTo(120)
make.height.equalTo(46)
}
}
}
19 changes: 0 additions & 19 deletions Milestone/Milestone/Global/Enum/AMPMStyle.swift

This file was deleted.

Loading

0 comments on commit 07e8074

Please sign in to comment.