diff --git a/DependencyGraph/mohanyang_dev_graph.png b/DependencyGraph/mohanyang_dev_graph.png index b5c0f31..fc98319 100644 Binary files a/DependencyGraph/mohanyang_dev_graph.png and b/DependencyGraph/mohanyang_dev_graph.png differ diff --git a/DependencyGraph/mohanyang_prod_graph.png b/DependencyGraph/mohanyang_prod_graph.png index 2d45945..e981c7a 100644 Binary files a/DependencyGraph/mohanyang_prod_graph.png and b/DependencyGraph/mohanyang_prod_graph.png differ diff --git a/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift b/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift index 43abfe4..8582974 100644 --- a/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift +++ b/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift @@ -14,7 +14,7 @@ import DependenciesMacros @DependencyClient public struct NetworkTracking { public var start: @Sendable () -> Void - public var updateNetworkConnected: @Sendable () -> AsyncThrowingStream = { .never } + public var updateNetworkConnected: @Sendable () -> AsyncStream = { .never } public var cancel: @Sendable () -> Void } diff --git a/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift b/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift index dded881..058a1a8 100644 --- a/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift +++ b/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift @@ -12,17 +12,21 @@ import NetworkTrackingInterface import Dependencies -extension NetworkTracking { +extension NetworkTracking: DependencyKey { public static let liveValue: NetworkTracking = .live() public static func live() -> NetworkTracking { let networkMonitor = NWPathMonitor() + let globalQueue = DispatchQueue.global() + return NetworkTracking( start: { - networkMonitor.start(queue: DispatchQueue.global()) + networkMonitor.start(queue: globalQueue) }, updateNetworkConnected: { - return AsyncThrowingStream { continuation in + return AsyncStream { continuation in + let initialState = networkMonitor.currentPath.status == .satisfied ? true : false + continuation.yield(initialState) networkMonitor.pathUpdateHandler = { path in let isConnected = path.status == .satisfied ? true : false continuation.yield(isConnected) diff --git a/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageCore.swift b/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageCore.swift index 15ffffd..5e6af9c 100644 --- a/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageCore.swift +++ b/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageCore.swift @@ -11,6 +11,7 @@ import APIClientInterface import UserServiceInterface import CatServiceInterface import UserDefaultsClientInterface +import NetworkTrackingInterface import ComposableArchitecture @@ -22,15 +23,17 @@ public struct MyPageCore { var cat: AnyCat? = nil var isTimerAlarmOn: Bool = false var isDisturbAlarmOn: Bool = false - var isInternetConnected: Bool = false + var isNetworkConnected: Bool = false let feedbackURLString: String = "https://forms.gle/wEUPH9Tvxgua4hCZ9" @Presents var myCat: MyCatCore.State? } public enum Action: BindableAction { case onAppear + case onDisappear case myCatDetailTapped case _responseUserInfo(UserDTO.Response.GetUserInfoResponseDTO) + case _fetchNetworkConntection(Bool) case myCat(PresentationAction) case binding(BindingAction) } @@ -38,6 +41,7 @@ public struct MyPageCore { @Dependency(APIClient.self) var apiClient @Dependency(UserService.self) var userService @Dependency(UserDefaultsClient.self) var userDefaultsClient + @Dependency(NetworkTracking.self) var networkTracking let isTimerAlarmOnKey = "mohanyang_userdefaults_isTimerAlarmOnKey" let isDisturbAlarmOnKey = "mohanyang_userdefaults_isDisturmAlarmOnKey" @@ -56,10 +60,17 @@ public struct MyPageCore { case .onAppear: state.isTimerAlarmOn = userDefaultsClient.boolForKey(isTimerAlarmOnKey) state.isDisturbAlarmOn = userDefaultsClient.boolForKey(isDisturbAlarmOnKey) - return .run { send in - let data = try await userService.getUserInfo(apiClient: apiClient) - await send(._responseUserInfo(data)) - } + return .merge( + .run { send in + let data = try await userService.getUserInfo(apiClient: apiClient) + await send(._responseUserInfo(data)) + }, + fetchNetworkConnection() + ) + + case .onDisappear: + networkTracking.cancel() + return .none case .myCatDetailTapped: guard let cat = state.cat else { return .none } @@ -74,6 +85,10 @@ public struct MyPageCore { ) return .none + case ._fetchNetworkConntection(let isConntected): + state.isNetworkConnected = isConntected + return .none + case .myCat: return .none @@ -91,4 +106,13 @@ public struct MyPageCore { return .none } } + + private func fetchNetworkConnection() -> Effect { + networkTracking.start() + return .run { send in + for await isConnected in networkTracking.updateNetworkConnected() { + await send(._fetchNetworkConntection(isConnected)) + } + } + } } diff --git a/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageView.swift index 2045ae1..b1189df 100644 --- a/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageView.swift @@ -14,11 +14,11 @@ import ComposableArchitecture public struct MyPageView: View { @Bindable var store: StoreOf - + public init(store: StoreOf) { self.store = store } - + public var body: some View { NavigationContainer( title: Text("마이페이지"), @@ -27,17 +27,22 @@ public struct MyPageView: View { ScrollView { VStack(spacing: Alias.Spacing.medium) { - MyCatSectionView(name: store.cat?.name ?? "") - .padding(.all, Alias.Spacing.xLarge) - .background( - RoundedRectangle(cornerRadius: Alias.BorderRadius.small) - .foregroundStyle(Alias.Color.Background.secondary) - ) - .onTapGesture { + MyCatSectionView( + name: store.cat?.name ?? "", + isNetworkConntected: $store.isNetworkConnected + ) + .padding(.all, Alias.Spacing.xLarge) + .background( + RoundedRectangle(cornerRadius: Alias.BorderRadius.small) + .foregroundStyle(Alias.Color.Background.secondary) + ) + .onTapGesture { + if store.isNetworkConnected { store.send(.myCatDetailTapped) } + } - StatisticSectionView(isInternetConnected: $store.isInternetConnected) + StatisticSectionView(isNetworkConnected: $store.isNetworkConnected) .padding(.all, Alias.Spacing.xLarge) .background( RoundedRectangle(cornerRadius: Alias.BorderRadius.medium) @@ -92,6 +97,9 @@ public struct MyPageView: View { .onAppear { store.send(.onAppear) } + .onDisappear { + store.send(.onDisappear) + } } private func openFeedbackForm(urlString: String) { @@ -103,6 +111,7 @@ public struct MyPageView: View { struct MyCatSectionView: View { let name: String + @Binding var isNetworkConntected: Bool var body: some View { HStack { @@ -115,19 +124,21 @@ struct MyCatSectionView: View { .foregroundStyle(Color.black) } Spacer() - DesignSystemAsset.Image._24ChevronRightPrimary.swiftUIImage + if isNetworkConntected { + DesignSystemAsset.Image._24ChevronRightPrimary.swiftUIImage + } } } } struct StatisticSectionView: View { - @Binding var isInternetConnected: Bool + @Binding var isNetworkConnected: Bool var body: some View { ZStack { VStack(spacing: Alias.Spacing.medium) { Spacer() - if isInternetConnected { + if isNetworkConnected { DesignSystemAsset.Image.imgUpdateStatistics.swiftUIImage } else { DesignSystemAsset.Image.imgOfflineStatistics.swiftUIImage @@ -153,7 +164,7 @@ struct AlarmSectionView: View { let title: String let subTitle: String @Binding var isOn: Bool - + var body: some View { HStack(spacing: 0) { VStack(alignment: .leading, spacing: Alias.Spacing.xSmall) {