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/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift index 24044ad..42e269d 100644 --- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift @@ -15,4 +15,5 @@ public enum Domain: String, Modulable { case UserService case CatService case PomodoroService + case NetworkTracking } diff --git a/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift b/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift new file mode 100644 index 0000000..4e431ac --- /dev/null +++ b/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift @@ -0,0 +1,22 @@ +// +// NetworkTrackingInterface.swift +// NetworkTracking +// +// Created by 김지현 on 8/21/24. +// + +import Foundation +import Network + +import Dependencies +import DependenciesMacros + +@DependencyClient +public struct NetworkTracking { + public var updateNetworkConnected: @Sendable () -> AsyncStream = { .never } +} + +extension NetworkTracking: TestDependencyKey { + public static let previewValue = Self() + public static let testValue = Self() +} diff --git a/Projects/Domain/NetworkTracking/Project.swift b/Projects/Domain/NetworkTracking/Project.swift new file mode 100644 index 0000000..7058008 --- /dev/null +++ b/Projects/Domain/NetworkTracking/Project.swift @@ -0,0 +1,20 @@ +import ProjectDescription +import ProjectDescriptionHelpers + +@_spi(Domain) +@_spi(Core) +import DependencyPlugin + +let project: Project = .makeTMABasedProject( + module: Domain.NetworkTracking, + scripts: [], + targets: [ + .sources, + .interface + ], + dependencies: [ + .interface: [ + .dependency(rootModule: Core.self) + ] + ] +) diff --git a/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift b/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift new file mode 100644 index 0000000..eea04d8 --- /dev/null +++ b/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift @@ -0,0 +1,41 @@ +// +// NetworkTracking.swift +// NetworkTracking +// +// Created by 김지현 on 8/21/24. +// + +import Foundation +import Network + +import NetworkTrackingInterface + +import Dependencies + +extension NetworkTracking: DependencyKey { + public static let liveValue: NetworkTracking = .live() + + public static func live() -> NetworkTracking { + let networkMonitor = NWPathMonitor() + let globalQueue = DispatchQueue.global() + + return NetworkTracking( + updateNetworkConnected: { + networkMonitor.start(queue: globalQueue) + 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) + } + + continuation.onTermination = { _ in + networkMonitor.cancel() + } + } + } + ) + } +} diff --git a/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift b/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift index 3dfcb66..b53d940 100644 --- a/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift +++ b/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift @@ -26,6 +26,7 @@ public struct NamingCatCore { var route: Route var selectedCat: AnyCat var text: String = "" + var isButtonDisabled: Bool = false var inputFieldError: NamingCatError? var tooltip: DownDirectionTooltip? = .init() var catRiv: RiveViewModel = Rive.catSelectRiv(stateMachineName: "State Machine_selectCat") @@ -62,6 +63,9 @@ public struct NamingCatCore { switch action { case .onAppear: + if state.route == .myPage { + state.isButtonDisabled = true + } state.catRiv.stop() state.catRiv.triggerInput(state.selectedCat.rivTriggerName) return .none @@ -99,6 +103,13 @@ public struct NamingCatCore { case .binding(\.text): state.inputFieldError = setError(state.text) + if state.text == "" && state.route == .myPage { + state.isButtonDisabled = true + } else if state.inputFieldError != nil { + state.isButtonDisabled = true + } else if state.inputFieldError == nil { + state.isButtonDisabled = false + } return .none case .binding: diff --git a/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatView.swift b/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatView.swift index 493fa21..af23f2d 100644 --- a/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatView.swift +++ b/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatView.swift @@ -34,7 +34,7 @@ public struct NamingCatView: View { store.catRiv.view() .setTooltipTarget(tooltip: DownDirectionTooltip.self) } - .frame(maxHeight: 240) + .frame(height: 240) VStack(spacing: Alias.Spacing.small) { HStack { @@ -57,7 +57,7 @@ public struct NamingCatView: View { store.send(.namedButtonTapped) } .buttonStyle(.box(level: .primary, size: .large, width: .low)) - .disabled(store.inputFieldError != nil) + .disabled(store.isButtonDisabled) .padding(.bottom, Alias.Spacing.small) } .padding(.horizontal, Alias.Spacing.xLarge) diff --git a/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageCore.swift b/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageCore.swift index 15ffffd..8e46fc2 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 task 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" @@ -61,6 +65,13 @@ public struct MyPageCore { await send(._responseUserInfo(data)) } + case .task: + return .run { send in + for await isConnected in networkTracking.updateNetworkConnected() { + await send(._fetchNetworkConntection(isConnected)) + } + } + case .myCatDetailTapped: guard let cat = state.cat else { return .none } state.myCat = MyCatCore.State(cat: cat) @@ -74,6 +85,10 @@ public struct MyPageCore { ) return .none + case ._fetchNetworkConntection(let isConntected): + state.isNetworkConnected = isConntected + return .none + case .myCat: return .none diff --git a/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/MyPage/MyPageView.swift index 2045ae1..231a50f 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) @@ -89,6 +94,9 @@ public struct MyPageView: View { ) { store in MyCatView(store: store) } + .task { + await store.send(.task).finish() + } .onAppear { store.send(.onAppear) } @@ -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) {