Skip to content

Commit

Permalink
Merge branch 'develop' into feat/#118
Browse files Browse the repository at this point in the history
  • Loading branch information
HELLOHIDI committed Nov 26, 2024
2 parents 36acdc5 + ae4355c commit d1272e3
Show file tree
Hide file tree
Showing 31 changed files with 912 additions and 583 deletions.
16 changes: 16 additions & 0 deletions HMH_Tuist_iOS/Projects/Core/Sources/Extension/Date+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,20 @@ public extension String {
formatter.dateFormat = format
return formatter.date(from: self)
}


func challengeHeaderFormattd() -> String? {
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "yyyy-MM-dd"
guard let date = inputDateFormatter.date(from: self) else {
return nil
}

let outputDateFormatter = DateFormatter()
outputDateFormatter.dateFormat = "M월 d일"
let formattedDateString = outputDateFormatter.string(from: date)

return formattedDateString
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ extension FinishedDailyChallenge {
return .init(challengeDate: challengeDate, isSuccess: isSuccess)
}
}

extension ChallengeSuccessInfo {
func toDTO() -> FinishedDailyChallenge {
return .init(challengeDate: challengeDate, isSuccess: isSuccess)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public struct ChallengeRepository: ChallengeRepositoryType {
.mapToDomainError(to: ChallengeError.self)
}

public func getSuccesChallenge() -> AnyPublisher<[String], ChallengeError> {
service.getSuccesChallenge()
public func postSuccesChallenge(sucessInfo: [ChallengeSuccessInfo]) -> AnyPublisher<[String], ChallengeError> {
let request = ChallengeSuccessRequest(finishedDailyChallenges: sucessInfo.map { $0.toDTO() })
return service.postSuccesChallenge(request: request)
.map { $0.statuses }
.mapToDomainError(to: ChallengeError.self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final public class MockChallengeService: ChallengeServiceType {
return getDailyChallengeResult
}

public func getSuccesChallenge() -> AnyPublisher<ChallengeSuccessResult, HMHNetworkError> {
public func postSuccesChallenge() -> AnyPublisher<ChallengeSuccessResult, HMHNetworkError> {
return getSuccesChallengeResult
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension ChallegeRepositoryTests {
.setFailureType(to: HMHNetworkError.self)
.eraseToAnyPublisher()

sut.getSuccesChallenge()
sut.postSuccesChallenge()
.sink(receiveCompletion: { completion in
if case .failure(let error) = completion {
XCTFail("챌린지 성공 여부 리스트 전송 API 변환 중 실패했습니다: 에러 \(error)")
Expand All @@ -45,7 +45,7 @@ extension ChallegeRepositoryTests {
for expected in testCases {
mockService.getSuccesChallengeResult = Fail(error: expected).eraseToAnyPublisher()

sut.getSuccesChallenge()
sut.postSuccesChallenge()
.sink(
receiveCompletion: handleCompletion(
expectedError: .networkError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,44 @@

import Foundation

@frozen public enum ChallengeStatus: String {
case EARNED
case FAILURE
case UNEARNED
}

public struct ChallengeDetail: Equatable {
let statuses: [String]
public struct ChallengeDetail {
let statuses: [PointStatusEnum]
let todayIndex: Int
let startDate: String
let challengeInfo: ChallengeInfo

public init(statuses: [String], todayIndex: Int, startDate: String, challengeInfo: ChallengeInfo) {
public enum InfoType {
case period
case goalTime
}

public init(statuses: [PointStatusEnum], todayIndex: Int, startDate: String, challengeInfo: ChallengeInfo) {
self.statuses = statuses
self.todayIndex = todayIndex
self.startDate = startDate
self.challengeInfo = challengeInfo
}

public func getTodayIndex() -> Int {
return todayIndex
}

public func getStatuses() -> [PointStatusEnum] {
return statuses
}

public func getStartDate() -> String {
return startDate
}

public func getChallengeInfo(_ infoType: InfoType) -> Int {
switch infoType {
case .period:
return challengeInfo.period
case .goalTime:
return challengeInfo.goalTime
}
}
}

public struct ChallengeInfo: Equatable {
Expand All @@ -38,4 +58,8 @@ public struct ChallengeInfo: Equatable {
self.goalTime = goalTime
self.apps = apps
}

public func getPeriod() -> Int {
return period
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
//

public struct ChallengeSuccessInfo {
let challengeDate: String
let isSuccess: Bool
public let challengeDate: String
public let isSuccess: Bool

public init(challengeDate: String, isSuccess: Bool) {
self.challengeDate = challengeDate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,35 @@ public struct PointDetail: Equatable {
self.period = period
self.pointStatuses = pointStatuses
}

public func getPoint() -> Int {
return point
}

public func getPeriod() -> Int {
return period
}

public func getPointStatuses() -> [PointStatuse] {
return pointStatuses
}
}

public struct PointStatuse: Equatable {
let date: String
let status: String
let status: PointStatusEnum

public init(date: String, status: String) {
self.date = date
self.status = status
self.status = PointStatusEnum(rawValue: status) ?? .none
}

public func getDate() -> String {
return date
}

public func getStatus() -> PointStatusEnum {
return status
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// PointStatusEnum.swift
// Domain
//
// Created by 이지희 on 11/4/24.
// Copyright © 2024 HMH-iOS. All rights reserved.
//

import SwiftUI

import DSKit

public enum PointStatusEnum: String {
case unearned = "UNEARNED"
case earned = "EARNED"
case failure = "FAILURE"
case none = "NONE"

public var buttonColor: Color {
switch self {
case .unearned:
return DSKitAsset.bluePurpleButton.swiftUIColor
case .earned:
return DSKitAsset.bluePurpleOpacity22.swiftUIColor
case .failure:
return DSKitAsset.gray6.swiftUIColor
case .none:
return DSKitAsset.gray7.swiftUIColor
}
}

public var titleColor: Color {
switch self {
case .unearned:
return DSKitAsset.whiteBtn.swiftUIColor
case .earned:
return DSKitAsset.bluePurpleOpacity70.swiftUIColor
case .failure:
return DSKitAsset.gray2.swiftUIColor
case .none:
return DSKitAsset.gray3.swiftUIColor
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Combine

public protocol ChallengeRepositoryType {
func getdailyChallenge() -> AnyPublisher<DailyChallengeInfo, ChallengeError>
func getSuccesChallenge() -> AnyPublisher<[String], ChallengeError>
func postSuccesChallenge(sucessInfo: [ChallengeSuccessInfo]) -> AnyPublisher<[String], ChallengeError>
func createChallenge(period: Int, goalTime: Int) -> AnyPublisher<Void, ChallengeError>
func getLockChallenge() -> AnyPublisher<Bool, ChallengeError>
func postLockChallenge() -> AnyPublisher<Void, ChallengeError>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//
// ChallengeUseCase.swift
// Domain
//
// Created by 이지희 on 11/16/24.
// Copyright © 2024 HMH-iOS. All rights reserved.
//

import Foundation
import Combine

import Core

public protocol ChallngeUseCaseType {
func createChallenge(
period: Int,
goalTime: Int
) -> AnyPublisher<Void, ChallengeError>

func getChallenge() -> AnyPublisher<ChallengeDetail, ChallengeError>
}

final class ChallengeUseCase: ChallngeUseCaseType {
private let repository: ChallengeRepositoryType

public init(repository: ChallengeRepositoryType) {
self.repository = repository
}

public func createChallenge(
period: Int,
goalTime: Int
) -> AnyPublisher<Void, ChallengeError> {
return repository.createChallenge(period: period, goalTime: goalTime)
.eraseToAnyPublisher()
}

public func getChallenge() -> AnyPublisher<ChallengeDetail, ChallengeError> {
repository.getChallenge()
.flatMap { [weak self] challengeDetail -> AnyPublisher<ChallengeDetail, ChallengeError> in
guard let self = self else {
return Fail(error: ChallengeError.networkError).eraseToAnyPublisher()
}

let noneDates = self.findNoneDates(
statuses: challengeDetail.statuses.map { $0.rawValue },
todayIndex: challengeDetail.todayIndex,
startDate: challengeDetail.startDate
)

guard !noneDates.isEmpty else {
// NONE 상태가 없으면 그대로 반환
return Just(challengeDetail)
.setFailureType(to: ChallengeError.self)
.eraseToAnyPublisher()
}

let successInfos = noneDates.map { date in
ChallengeSuccessInfo(challengeDate: date, isSuccess: true)
}

return self.sendSucessChallenge(challengeSucessInfo: successInfos)
.flatMap { _ -> AnyPublisher<ChallengeDetail, ChallengeError> in
// 서버 전송 후 상태를 업데이트하여 반환
let updatedStatuses = challengeDetail.statuses.enumerated().map { index, status -> PointStatusEnum in
if noneDates.contains(self.dateString(for: index, startDate: challengeDetail.startDate)) {
return .unearned
}
return status
}

let updatedChallenge = ChallengeDetail(
statuses: updatedStatuses,
todayIndex: challengeDetail.todayIndex,
startDate: challengeDetail.startDate,
challengeInfo: challengeDetail.challengeInfo
)

return Just(updatedChallenge)
.setFailureType(to: ChallengeError.self)
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}

private func sendSucessChallenge(challengeSucessInfo: [ChallengeSuccessInfo]) -> AnyPublisher<[String], ChallengeError> {
return repository.postSuccesChallenge(sucessInfo: challengeSucessInfo)
.eraseToAnyPublisher()
}

private func findNoneDates(statuses: [String], todayIndex: Int, startDate: String) -> [String] {
var dates: [String] = []

let challengeDays = statuses.count

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

guard let start = dateFormatter.date(from: startDate) else {
print("Invalid start date format")
return dates
}

let calendar = Calendar.current

if todayIndex > 0 {
for index in 0 ..< todayIndex {
if statuses[index] == "NONE" {
if let newDate = calendar.date(byAdding: .day, value: index, to: start) {
let formattedDate = dateFormatter.string(from: newDate)
dates.append(formattedDate)
}
}
}
} else {
for index in 0 ..< challengeDays {
if statuses[index] == "NONE" {
if let newDate = calendar.date(byAdding: .day, value: index, to: start) {
let formattedDate = dateFormatter.string(from: newDate)
dates.append(formattedDate)
}
}
}
}

return dates
}

private func dateString(for index: Int, startDate: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

guard let start = dateFormatter.date(from: startDate),
let newDate = Calendar.current.date(byAdding: .day, value: index, to: start) else {
return ""
}
return dateFormatter.string(from: newDate)
}
}
Loading

0 comments on commit d1272e3

Please sign in to comment.