Skip to content

Commit 1524b1a

Browse files
Release/1.0.8 -> develop (#285)
* chore: 빌드 넘버 1.0.8 (29) * chore: DomainAuth 의존성 추가 * feat: ProfileEditView bottom ignoreSafeArea 추가 * [Feature/#277] 웹뷰 os type, 버전 파라미터 추가 (#279) * [Feature/#278] 웹뷰 상단 safe area 무시하도록 수정 (#284) * [Fix/#280] 로그인화면 백그라운드 이미지 비율 수정 (#281) * [Fix/#282] 로그인 화면 로그인 버튼 vstack 하단 마진 수정 (#283) * [Feature/#232] 커스텀 alert 적용 (#272) * feat: SplashView bottleAlert 적용 * feat: PingPongDetailView bottleAlert 적용 * feat: ReportUserView bottleAlert 적용 * feat: SandBeachView bottleAlert 적용 * feat: 탈퇴하기 Alert message 수정 (#274) * [Feature/#275] 알림 권한 미허용 시 alert 추가 (#276) * feat: AppDelegate 푸시 수신 상태 Notification 등록 * feat: UserClient 푸시 알림 허용 상태 로직 추가 * feat: 푸시 알림 허용 상태에 따른 알림설정 화면 로직 구현 * feat: UserClient 푸쉬알림허용상태 Publisher 구현 * feat: 푸쉬알림허용상태에 따른 로직 변경 * feat: 토글 버튼 binding 코드 개선 - 코드리뷰 반영 * feat: UserClient UserDefaultKeys enum 추가 * feat: 오탈자 수정 - pushNotificationSubject -> pushNotificationAllowStatusSubject * chore: 빌드 넘버 1.0.8 (30) --------- Co-authored-by: JongHoon <qnm541@gmail.com>
1 parent 97e6980 commit 1524b1a

File tree

32 files changed

+365
-194
lines changed

32 files changed

+365
-194
lines changed

Projects/App/Sources/AppDelegate.swift

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
2828
UIApplication.shared.registerForRemoteNotifications()
2929
UNUserNotificationCenter.current().delegate = self
3030
Messaging.messaging().delegate = self
31-
31+
setNotification()
3232
application.registerForRemoteNotifications()
33-
3433
store.send(.appDelegate(.didFinishLunching))
3534
return true
3635
}
3736
}
3837

38+
// MARK: - UNUserNotificationCenterDelegate
3939
extension AppDelegate: UNUserNotificationCenterDelegate {
4040
func messaging(
4141
_ messaging: Messaging,
@@ -62,3 +62,41 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
6262
return [.badge, .sound, .banner, .list]
6363
}
6464
}
65+
66+
// MARK: - objc funcs
67+
private extension AppDelegate {
68+
@objc func checkPushNotificationStatus() {
69+
UNUserNotificationCenter.current()
70+
.getNotificationSettings { [weak self] permission in
71+
guard let self = self else { return }
72+
DispatchQueue.main.async {
73+
switch permission.authorizationStatus {
74+
case .notDetermined:
75+
self.store.send(.appDelegate(.pushNotificationAllowStatusDidChanged(isAllow: true)))
76+
case .denied:
77+
self.store.send(.appDelegate(.pushNotificationAllowStatusDidChanged(isAllow: false)))
78+
case .authorized:
79+
self.store.send(.appDelegate(.pushNotificationAllowStatusDidChanged(isAllow: true)))
80+
case .provisional:
81+
self.store.send(.appDelegate(.pushNotificationAllowStatusDidChanged(isAllow: false)))
82+
case .ephemeral:
83+
self.store.send(.appDelegate(.pushNotificationAllowStatusDidChanged(isAllow: true)))
84+
@unknown default:
85+
Log.error("Unknow Notification Status")
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
// MARK: - Private Methods
93+
private extension AppDelegate {
94+
func setNotification() {
95+
NotificationCenter.default.addObserver(
96+
self,
97+
selector: #selector(checkPushNotificationStatus),
98+
name: UIApplication.willEnterForegroundNotification,
99+
object: nil
100+
)
101+
}
102+
}

Projects/Domain/Auth/Project.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ let project = Project.makeModule(
1818
factory: .init(
1919
dependencies: [
2020
.domain(interface: .Auth),
21-
.domain(interface: .Error)
21+
.domain(interface: .Error),
22+
.domain(implements: .User)
2223
]
2324
)
2425
),

Projects/Domain/User/Interface/Sources/UserClient.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@
77

88
import Foundation
99

10+
import Combine
11+
1012
public struct UserClient {
1113
private let _isLoggedIn: () -> Bool
1214
private let _isAppDeleted: () -> Bool
1315
private let _fetchFcmToken: () -> String?
1416
private let updateLoginState: (Bool) -> Void
1517
private let updateDeleteState: (Bool) -> Void
1618
private let updateFcmToken: (String) -> Void
19+
private let updatePushNotificationAllowStatus: (Bool) -> Void
1720
private let _fetchAlertState: () async throws -> [UserAlertState]
21+
private let _fetchPushNotificationAllowStatus: () -> Bool
1822
private let updateAlertState: (UserAlertState) async throws -> Void
1923
private let fetchContacts: () async throws -> [String]
2024
private let updateBlockContacts: ([String]) async throws -> Void
25+
private let pushNotificationAllowStatusSubject = CurrentValueSubject<Bool, Never>(true)
26+
27+
public var pushNotificationAllowStatusPublisher: AnyPublisher<Bool, Never> {
28+
return pushNotificationAllowStatusSubject.eraseToAnyPublisher()
29+
}
2130

2231
public init(
2332
isLoggedIn: @escaping () -> Bool,
@@ -26,7 +35,9 @@ public struct UserClient {
2635
updateLoginState: @escaping (Bool) -> Void,
2736
updateDeleteState: @escaping (Bool) -> Void,
2837
updateFcmToken: @escaping (String) -> Void,
38+
updatePushNotificationAllowStatus: @escaping (Bool) -> Void,
2939
fetchAlertState: @escaping () async throws -> [UserAlertState],
40+
fetchPushNotificationAllowStatus: @escaping () -> Bool,
3041
updateAlertState: @escaping (UserAlertState) async throws -> Void,
3142
fetchContacts: @escaping () async throws -> [String],
3243
updateBlockContacts: @escaping ([String]) async throws -> Void
@@ -37,7 +48,9 @@ public struct UserClient {
3748
self.updateLoginState = updateLoginState
3849
self.updateDeleteState = updateDeleteState
3950
self.updateFcmToken = updateFcmToken
51+
self.updatePushNotificationAllowStatus = updatePushNotificationAllowStatus
4052
self._fetchAlertState = fetchAlertState
53+
self._fetchPushNotificationAllowStatus = fetchPushNotificationAllowStatus
4154
self.updateAlertState = updateAlertState
4255
self.fetchContacts = fetchContacts
4356
self.updateBlockContacts = updateBlockContacts
@@ -67,10 +80,19 @@ public struct UserClient {
6780
updateFcmToken(fcmToken)
6881
}
6982

83+
public func updatePushNotificationAllowStatus(isAllow: Bool) {
84+
pushNotificationAllowStatusSubject.send(isAllow)
85+
updatePushNotificationAllowStatus(isAllow)
86+
}
87+
7088
public func fetchAlertState() async throws -> [UserAlertState] {
7189
try await _fetchAlertState()
7290
}
7391

92+
public func fetchPushNotificationAllowStatus() -> Bool {
93+
_fetchPushNotificationAllowStatus()
94+
}
95+
7496
public func updateAlertState(alertState: UserAlertState) async throws {
7597
try await updateAlertState(alertState)
7698
}

Projects/Domain/User/Sources/UserClient.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,56 @@ import ComposableArchitecture
1717
import Moya
1818

1919
extension UserClient: DependencyKey {
20+
private enum UserDefaultsKeys: String {
21+
case loginState
22+
case deleteState
23+
case fcmToken
24+
case alertAllowState
25+
}
26+
2027
static public var liveValue: UserClient = .live()
2128

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

2532
return .init(
2633
isLoggedIn: {
27-
return UserDefaults.standard.bool(forKey: "loginState")
34+
return UserDefaults.standard.bool(forKey: UserDefaultsKeys.loginState.rawValue)
2835
},
2936

3037
isAppDeleted: {
31-
return !UserDefaults.standard.bool(forKey: "deleteState")
38+
return !UserDefaults.standard.bool(forKey: UserDefaultsKeys.deleteState.rawValue)
3239
},
3340

3441
fetchFcmToken: {
35-
return UserDefaults.standard.string(forKey: "fcmToken")
42+
return UserDefaults.standard.string(forKey: UserDefaultsKeys.fcmToken.rawValue)
3643
},
3744

3845
updateLoginState: { isLoggedIn in
39-
UserDefaults.standard.set(isLoggedIn, forKey: "loginState")
46+
UserDefaults.standard.set(isLoggedIn, forKey: UserDefaultsKeys.loginState.rawValue)
4047
},
4148

4249
updateDeleteState: { isDelete in
43-
UserDefaults.standard.set(!isDelete, forKey: "deleteState")
50+
UserDefaults.standard.set(!isDelete, forKey: UserDefaultsKeys.deleteState.rawValue)
4451
},
4552

4653
updateFcmToken: { fcmToken in
47-
UserDefaults.standard.set(fcmToken, forKey: "fcmToken")
54+
UserDefaults.standard.set(fcmToken, forKey: UserDefaultsKeys.fcmToken.rawValue)
55+
},
56+
57+
updatePushNotificationAllowStatus: { isAllow in
58+
UserDefaults.standard.set(isAllow, forKey: UserDefaultsKeys.alertAllowState.rawValue)
4859
},
4960

5061
fetchAlertState: {
5162
let responseData = try await networkManager.reqeust(api: .apiType(UserAPI.fetchAlertState), dto: [AlertStateResponseDTO].self)
5263
return responseData.map { $0.toDomain() }
53-
5464
},
65+
66+
fetchPushNotificationAllowStatus: {
67+
return UserDefaults.standard.bool(forKey: UserDefaultsKeys.alertAllowState.rawValue)
68+
},
69+
5570
updateAlertState: { alertState in
5671
let requestData = AlertStateRequestDTO(alertType: alertState.alertType, enabled: alertState.enabled)
5772
try await networkManager.reqeust(api: .apiType(UserAPI.updateAlertState(reqeustData: requestData)))

Projects/Feature/BaseWebView/Interface/Sources/BaseWebViewType.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77

88
import Foundation
99

10+
import DomainApplicationInterface
11+
import DomainApplication
12+
1013
import CoreWebViewInterface
1114
import CoreKeyChainStoreInterface
1215
import CoreKeyChainStore
1316

17+
import Dependencies
18+
1419
public enum BottleWebViewType {
1520
private var baseURL: String {
1621
(Bundle.main.infoDictionary?["WEB_VIEW_BASE_URL"] as? String) ?? ""
@@ -67,11 +72,15 @@ public enum BottleWebViewType {
6772
// MARK: - private methods
6873
private extension BottleWebViewType {
6974
func makeUrlWithToken(_ path: String) -> URL {
75+
@Dependency(\.applicationClient) var applicationClient
76+
7077
var components = URLComponents(string: baseURL)
7178
components?.path = "/\(path)"
7279
components?.queryItems = [
7380
URLQueryItem(name: "accessToken", value: KeyChainTokenStore.shared.load(property: .accessToken)),
74-
URLQueryItem(name: "refreshToken", value: KeyChainTokenStore.shared.load(property: .refreshToken))
81+
URLQueryItem(name: "refreshToken", value: KeyChainTokenStore.shared.load(property: .refreshToken)),
82+
URLQueryItem(name: "device", value: "ios"),
83+
URLQueryItem(name: "version", value: applicationClient.fetchCurrentAppVersion())
7584
]
7685

7786
return (components?.url)!

Projects/Feature/BottleArrival/Interface/Sources/BottleArrivalView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public struct BottleArrivalView: View {
4343
LoadingIndicator()
4444
}
4545
}
46-
.ignoresSafeArea(.all, edges: .bottom)
46+
.ignoresSafeArea(.all, edges: [.top, .bottom])
4747
.toolbar(.hidden, for: .navigationBar)
4848
.toolbar(.hidden, for: .bottomBar)
4949
}

Projects/Feature/BottleStorage/Interface/Sources/PingPongDetail/View/PingPongDetail/PingPongDetailFeature.swift

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77

88
import Foundation
99

10-
import CoreLoggerInterface
1110
import FeatureReportInterface
1211
import DomainBottle
1312

13+
import CoreLoggerInterface
14+
1415
import ComposableArchitecture
1516

1617
extension PingPongDetailFeature {
@@ -42,12 +43,46 @@ extension PingPongDetailFeature {
4243
imageURL: imageURL ?? "", userID: userId ?? -1, userName: userName, userAge: userAge ?? -1)
4344
return .send(.delegate(.reportButtonDidTapped(userReportProfile)))
4445

46+
case .stopTalkAlertDidRequired:
47+
state.destination = .alert(.init(
48+
title: { TextState("중단하기") },
49+
actions: {
50+
ButtonState(
51+
role: .cancel,
52+
action: .dismiss,
53+
label: { TextState("계속하기")})
54+
55+
ButtonState(
56+
role: .destructive,
57+
action: .confirmStopTalk,
58+
label: { TextState("중단하기") })
59+
},
60+
message: { TextState("중단 시 모든 핑퐁 내용이 사라져요. 정말 중단하시겠어요?") }
61+
))
62+
return .none
63+
64+
// Destination
65+
case let .destination(.presented(.alert(alert))):
66+
switch alert {
67+
case .confirmStopTalk:
68+
return .run { [bottleID = state.bottleID] send in
69+
try await bottleClient.stopTalk(bottleID: bottleID)
70+
await send(.delegate(.popToRootDidRequired))
71+
}
72+
73+
case .dismiss:
74+
state.destination = nil
75+
return .none
76+
}
77+
78+
// Introduction Delegate
4579
case let .introduction(.delegate(delegate)):
4680
switch delegate {
47-
case .popToRootDidRequired:
48-
return .send(.delegate(.popToRootDidRequired))
81+
case .stopTaskButtonTapped:
82+
return .send(.stopTalkAlertDidRequired)
4983
}
50-
84+
85+
// QuestionAndAnswer Delegate
5186
case let .questionAndAnswer(.delegate(delegate)):
5287
switch delegate {
5388
case .reloadPingPongRequired:
@@ -56,8 +91,11 @@ extension PingPongDetailFeature {
5691
return .send(.delegate(.popToRootDidRequired))
5792
case .refreshPingPong:
5893
return fetchPingPong(state: &state)
94+
case .stopTaskButtonDidTapped:
95+
return .send(.stopTalkAlertDidRequired)
5996
}
60-
97+
98+
// Matching Delegate
6199
case let .matching(.delegate(delegate)):
62100
switch delegate {
63101
case .otherBottleButtonDidTapped:

Projects/Feature/BottleStorage/Interface/Sources/PingPongDetail/View/PingPongDetail/PingPongDetailFeatureInterface.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public struct PingPongDetailFeature {
4343
var matching: MatchingFeature.State
4444
var selectedTab: PingPongDetailViewTabType
4545

46+
@Presents var destination: Destination.State?
47+
4648
public init(
4749
bottleID: Int,
4850
isRead: Bool,
@@ -67,7 +69,7 @@ public struct PingPongDetailFeature {
6769
case pingPongDidFetched(_: BottlePingPong)
6870
case backButtonDidTapped
6971
case reportButtonDidTapped
70-
72+
case stopTalkAlertDidRequired
7173

7274
// Delegate
7375
case delegate(Delegate)
@@ -83,6 +85,13 @@ public struct PingPongDetailFeature {
8385
case questionAndAnswer(QuestionAndAnswerFeature.Action)
8486
case matching(MatchingFeature.Action)
8587
case binding(BindingAction<State>)
88+
case destination(PresentationAction<Destination.Action>)
89+
// Alert
90+
case alert(Alert)
91+
public enum Alert: Equatable {
92+
case confirmStopTalk
93+
case dismiss
94+
}
8695
}
8796

8897
public var body: some ReducerOf<Self> {
@@ -97,6 +106,15 @@ public struct PingPongDetailFeature {
97106
MatchingFeature()
98107
}
99108
reducer
109+
.ifLet(\.$destination, action: \.destination)
100110
}
101111
}
102112

113+
// MARK: - Destination
114+
115+
extension PingPongDetailFeature {
116+
@Reducer(state: .equatable)
117+
public enum Destination {
118+
case alert(AlertState<PingPongDetailFeature.Action.Alert>)
119+
}
120+
}

Projects/Feature/BottleStorage/Interface/Sources/PingPongDetail/View/PingPongDetail/PingPongDetailView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public struct PingPongDetailView: View {
5555
}
5656
)
5757
.ignoresSafeArea(.all, edges: .bottom)
58+
.bottleAlert($store.scope(state: \.destination?.alert, action: \.destination.alert))
5859
}
5960
}
6061

0 commit comments

Comments
 (0)