Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/#252] 마이페이지 알림설정 구현 #253

Merged
merged 13 commits into from
Sep 22, 2024
Merged
46 changes: 46 additions & 0 deletions Projects/Domain/User/Interface/Sources/API/UserAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// UserAPI.swift
// DomainUserInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

import CoreNetworkInterface

import Moya

public enum UserAPI {
case fetchAlertState
case updateAlertState(reqeustData: AlertStateRequestDTO)
}

extension UserAPI: BaseTargetType {
public var path: String {
switch self {
case .fetchAlertState:
return "api/v1/user/alimy"
case .updateAlertState:
return "api/v1/user/alimy"
}
}

public var method: Moya.Method {
switch self {
case .fetchAlertState:
return .get
case .updateAlertState:
return .post
}
}

public var task: Moya.Task {
switch self {
case .fetchAlertState:
return .requestPlain
case .updateAlertState(let requestData):
return .requestJSONEncodable(requestData)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// AlertStateRequestDTO.swift
// DomainUserInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

public struct AlertStateRequestDTO: Encodable {
public let alimyType: AlertType.RawValue
public let enabled: Bool

public init(alertType: AlertType, enabled: Bool) {
self.alimyType = alertType.rawValue
self.enabled = enabled
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// AlertStateReponseDTO.swift
// DomainUserInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

public struct AlertStateResponseDTO: Decodable {
let alimyType: String
let enabled: Bool

public func toDomain() -> UserAlertState {
return .init(
alertType: AlertType(rawValue: alimyType) ?? .none,
enabled: enabled
)
}
}
21 changes: 21 additions & 0 deletions Projects/Domain/User/Interface/Sources/Entity/AlertState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// AlertState.swift
// DomainUserInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

public struct UserAlertState {
public let alertType: AlertType
public let enabled: Bool

public init(
alertType: AlertType,
enabled: Bool
) {
self.alertType = alertType
self.enabled = enabled
}
}
16 changes: 16 additions & 0 deletions Projects/Domain/User/Interface/Sources/Entity/AlertType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// AlertType.swift
// DomainUserInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

public enum AlertType: String, Codable {
case none = "NONE"
case randomBottle = "DAILY_RANDOM"
case arrivalBottle = "RECEIVE_LIKE"
case pingpong = "PINGPONG"
case marketing = "MARKETING"
}
17 changes: 16 additions & 1 deletion Projects/Domain/User/Interface/Sources/UserClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,28 @@ public struct UserClient {
private let updateLoginState: (Bool) -> Void
private let updateDeleteState: (Bool) -> Void
private let updateFcmToken: (String) -> Void
private let _fetchAlertState: () async throws -> [UserAlertState]
private let updateAlertState: (UserAlertState) async throws -> Void


public init(
isLoggedIn: @escaping () -> Bool,
isAppDeleted: @escaping () -> Bool,
fetchFcmToken: @escaping () -> String?,
updateLoginState: @escaping (Bool) -> Void,
updateDeleteState: @escaping (Bool) -> Void,
updateFcmToken: @escaping (String) -> Void
updateFcmToken: @escaping (String) -> Void,
fetchAlertState: @escaping () async throws -> [UserAlertState],
updateAlertState: @escaping (UserAlertState) async throws -> Void
) {
self._isLoggedIn = isLoggedIn
self._isAppDeleted = isAppDeleted
self._fetchFcmToken = fetchFcmToken
self.updateLoginState = updateLoginState
self.updateDeleteState = updateDeleteState
self.updateFcmToken = updateFcmToken
self._fetchAlertState = fetchAlertState
self.updateAlertState = updateAlertState
}

public func isLoggedIn() -> Bool {
Expand All @@ -54,4 +61,12 @@ public struct UserClient {
public func updateFcmToken(fcmToken: String) {
updateFcmToken(fcmToken)
}

public func fetchAlertState() async throws -> [UserAlertState] {
try await _fetchAlertState()
}

public func updateAlertState(alertState: UserAlertState) async throws {
try await updateAlertState(alertState)
}
}
15 changes: 15 additions & 0 deletions Projects/Domain/User/Sources/UserClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import Foundation
import DomainUserInterface

import CoreKeyChainStore
import CoreNetwork

import ComposableArchitecture
import Moya

extension UserClient: DependencyKey {
static public var liveValue: UserClient = .live()

static func live() -> UserClient {
@Dependency(\.network) var networkManager

return .init(
isLoggedIn: {
return UserDefaults.standard.bool(forKey: "loginState")
Expand All @@ -40,6 +44,17 @@ extension UserClient: DependencyKey {

updateFcmToken: { fcmToken in
UserDefaults.standard.set(fcmToken, forKey: "fcmToken")
},

fetchAlertState: {
let responseData = try await networkManager.reqeust(api: .apiType(UserAPI.fetchAlertState), dto: [AlertStateResponseDTO].self)
return responseData.map { $0.toDomain() }

},

updateAlertState: { alertState in
let requestData = AlertStateRequestDTO(alertType: alertState.alertType, enabled: alertState.enabled)
try await networkManager.reqeust(api: .apiType(UserAPI.updateAlertState(reqeustData: requestData)))
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// AlertSettingFeature.swift
// FeatureMyPageInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

import DomainUser
import DomainUserInterface

import ComposableArchitecture

extension AlertSettingFeature {
public init() {
@Dependency(\.userClient) var userClient
@Dependency(\.dismiss) var dismiss

let reducer = Reduce<State, Action> { state, action in
switch action {
case .onLoad:
return .run { send in
let alertStateList = try await userClient.fetchAlertState()

for alertState in alertStateList {
let isOn = alertState.enabled
switch alertState.alertType {
case .randomBottle:
await send(.randomBottleToggleDidFetched(isOn: isOn))
case .arrivalBottle:
await send(.arrivalBottleToggleDidFetched(isOn: isOn))
case .pingpong:
await send(.pingpongToggleDidFetched(isOn: isOn))
case .marketing:
await send(.marketingToggleDidFetched(isOn: isOn))
default:
break
}
}
}

case let .randomBottleToggleDidFetched(isOn):
state.isOnRandomBottleToggle = isOn
return .none

case let .arrivalBottleToggleDidFetched(isOn):
state.isOnArrivalBottleToggle = isOn
return .none

case let .pingpongToggleDidFetched(isOn):
state.isOnPingPongToggle = isOn
return .none

case let .marketingToggleDidFetched(isOn):
state.isOnMarketingToggle = isOn
return .none

case .backButtonDidTapped:
return .run { _ in
await dismiss()
}

case .binding(\.isOnRandomBottleToggle):
return .run { [isOn = state.isOnRandomBottleToggle] send in
await send(.toggleDidChanged(alertState: .init(alertType: .randomBottle, enabled: isOn)))
}
.debounce(
id: ID.randomBottle,
for: 1.0,
scheduler: DispatchQueue.main)

case .binding(\.isOnArrivalBottleToggle):
return .run { [isOn = state.isOnArrivalBottleToggle] send in
await send(.toggleDidChanged(alertState: .init(alertType: .arrivalBottle, enabled: isOn)))
}
.debounce(
id: ID.arrivalBottle,
for: 1.0,
scheduler: DispatchQueue.main)

case .binding(\.isOnPingPongToggle):
return .run { [isOn = state.isOnPingPongToggle] send in
await send(.toggleDidChanged(alertState: .init(alertType: .pingpong, enabled: isOn)))
}
.debounce(
id: ID.pingping,
for: 1.0,
scheduler: DispatchQueue.main)

case .binding(\.isOnMarketingToggle):
return .run { [isOn = state.isOnMarketingToggle] send in
await send(.toggleDidChanged(alertState: .init(alertType: .marketing, enabled: isOn)))
}
.debounce(
id: ID.marketing,
for: 1.0,
scheduler: DispatchQueue.main)

case let .toggleDidChanged(alertState):
return .run { send in
try await userClient.updateAlertState(alertState: alertState)
}

default:
return .none
}
}

self.init(reducer: reducer)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// AlertSettingFeatureInterface.swift
// FeatureMyPageInterface
//
// Created by 임현규 on 9/21/24.
//

import Foundation

import DomainUserInterface

import ComposableArchitecture

@Reducer
public struct AlertSettingFeature {
private let reducer: Reduce<State, Action>

public init(reducer: Reduce<State, Action>) {
self.reducer = reducer
}

@ObservableState
public struct State: Equatable {
public var isOnRandomBottleToggle: Bool
public var isOnArrivalBottleToggle: Bool
public var isOnPingPongToggle: Bool
public var isOnMarketingToggle: Bool

public init(
isOnRandomBottleToggle: Bool = false,
isOnArrivalBottleToggle: Bool = false,
isOnPingPongToggle: Bool = false,
isOnMarketingToggle: Bool = false
) {
self.isOnRandomBottleToggle = isOnRandomBottleToggle
self.isOnArrivalBottleToggle = isOnArrivalBottleToggle
self.isOnPingPongToggle = isOnPingPongToggle
self.isOnMarketingToggle = isOnMarketingToggle
}
}

public enum Action: BindableAction {
case onLoad

case randomBottleToggleDidFetched(isOn: Bool)
case arrivalBottleToggleDidFetched(isOn: Bool)
case pingpongToggleDidFetched(isOn: Bool)
case marketingToggleDidFetched(isOn: Bool)

// UserAction
case toggleDidChanged(alertState: UserAlertState)
case backButtonDidTapped

case binding(BindingAction<State>)
}

enum ID: Hashable {
case randomBottle
case arrivalBottle
case pingping
case marketing
}

public var body: some ReducerOf<Self> {
BindingReducer()
reducer
}
}
Loading