diff --git a/DependencyGraph/mohanyang_dev_graph.png b/DependencyGraph/mohanyang_dev_graph.png index fc98319..b1a7ba6 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 e981c7a..4a65a1a 100644 Binary files a/DependencyGraph/mohanyang_prod_graph.png and b/DependencyGraph/mohanyang_prod_graph.png differ diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Core.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Core.swift index 8e3b0dd..96b272f 100644 --- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Core.swift +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Core.swift @@ -15,4 +15,5 @@ public enum Core: String, Modulable { case DatabaseClient case UserDefaultsClient case FeedbackGeneratorClient + case NetworkTracking } diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift index 42e269d..24044ad 100644 --- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Module/Domain.swift @@ -15,5 +15,4 @@ public enum Domain: String, Modulable { case UserService case CatService case PomodoroService - case NetworkTracking } diff --git a/Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift b/Projects/Core/NetworkTracking/Interface/NetworkTrackingInterface.swift similarity index 100% rename from Projects/Domain/NetworkTracking/Interface/NetworkTrackingInterface.swift rename to Projects/Core/NetworkTracking/Interface/NetworkTrackingInterface.swift diff --git a/Projects/Domain/NetworkTracking/Project.swift b/Projects/Core/NetworkTracking/Project.swift similarity index 73% rename from Projects/Domain/NetworkTracking/Project.swift rename to Projects/Core/NetworkTracking/Project.swift index 7058008..6b386b7 100644 --- a/Projects/Domain/NetworkTracking/Project.swift +++ b/Projects/Core/NetworkTracking/Project.swift @@ -1,12 +1,12 @@ import ProjectDescription import ProjectDescriptionHelpers -@_spi(Domain) @_spi(Core) +@_spi(Shared) import DependencyPlugin let project: Project = .makeTMABasedProject( - module: Domain.NetworkTracking, + module: Core.NetworkTracking, scripts: [], targets: [ .sources, @@ -14,7 +14,7 @@ let project: Project = .makeTMABasedProject( ], dependencies: [ .interface: [ - .dependency(rootModule: Core.self) + .dependency(rootModule: Shared.self) ] ] ) diff --git a/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift b/Projects/Core/NetworkTracking/Sources/NetworkTracking.swift similarity index 90% rename from Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift rename to Projects/Core/NetworkTracking/Sources/NetworkTracking.swift index eea04d8..d3ae7a6 100644 --- a/Projects/Domain/NetworkTracking/Sources/NetworkTracking.swift +++ b/Projects/Core/NetworkTracking/Sources/NetworkTracking.swift @@ -16,11 +16,11 @@ extension NetworkTracking: DependencyKey { public static let liveValue: NetworkTracking = .live() public static func live() -> NetworkTracking { - let networkMonitor = NWPathMonitor() - let globalQueue = DispatchQueue.global() - return NetworkTracking( updateNetworkConnected: { + let networkMonitor = NWPathMonitor() + let globalQueue = DispatchQueue.global() + networkMonitor.start(queue: globalQueue) return AsyncStream { continuation in let initialState = networkMonitor.currentPath.status == .satisfied ? true : false diff --git a/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift b/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift index aa91bc7..fef8485 100644 --- a/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift +++ b/Projects/Feature/HomeFeature/Sources/Home/HomeCore.swift @@ -9,6 +9,7 @@ import UserNotifications import PomodoroFeature +import NetworkTrackingInterface import PushService import CatServiceInterface import PomodoroServiceInterface @@ -33,6 +34,8 @@ public struct HomeCore { // 저장된 고양이 불러오고나서 이 state에 저장하면 될듯합니다 var selectedCat: AnyCat = CatFactory.makeCat(type: .threeColor, no: 0, name: "치즈냥") + var isNetworkConnected: Bool = false + var toast: DefaultToast? var dialog: DefaultDialog? @@ -48,6 +51,7 @@ public struct HomeCore { public enum Action: BindableAction { case binding(BindingAction) + case task case onLoad case onAppear case setHomeCatTooltip(HomeCatDialogueTooltip?) @@ -58,7 +62,8 @@ public struct HomeCore { case restTimeButtonTapped case mypageButtonTappd case playButtonTapped - + case _fetchNetworkConnection(Bool) + case syncCategory case categorySelect(PresentationAction) @@ -71,6 +76,7 @@ public struct HomeCore { @Dependency(DatabaseClient.self) var databaseClient @Dependency(APIClient.self) var apiClient @Dependency(PomodoroService.self) var pomodoroService + @Dependency(NetworkTracking.self) var networkTracking let isHomeGuideCompletedKey = "mohanyang_userdefaults_isHomeGuideCompleted" public init() {} @@ -96,7 +102,14 @@ public struct HomeCore { switch action { case .binding: return .none - + + case .task: + return .run { send in + for await isConnected in networkTracking.updateNetworkConnected() { + await send(._fetchNetworkConnection(isConnected)) + } + } + case .onLoad: return .run { send in await send(.setHomeCatTooltip(nil)) @@ -146,7 +159,11 @@ public struct HomeCore { case .playButtonTapped: state.focusPomodoro = .init() return .none - + + case let ._fetchNetworkConnection(isConnected): + state.isNetworkConnected = isConnected + return .none + case .syncCategory: return .run { send in try await self.pomodoroService.syncCategoryList( diff --git a/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift b/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift index bf71380..8d1641a 100644 --- a/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift +++ b/Projects/Feature/HomeFeature/Sources/Home/HomeView.swift @@ -104,6 +104,27 @@ public struct HomeView: View { } } .background(Global.Color.gray50) + .overlay { + if !store.isNetworkConnected { + VStack { + HStack(spacing: Alias.Spacing.small) { + DesignSystemAsset.Image._16NullPrimary.swiftUIImage + Text("오프라인 모드") + .font(Typography.bodySB) + .foregroundStyle(Alias.Color.Text.secondary) + } + .padding(.horizontal, Alias.Spacing.large) + .padding(.vertical, Alias.Spacing.small) + .background { + RoundedRectangle(cornerRadius: Alias.BorderRadius.max) + .foregroundStyle(Global.Color.white) + .shadow(radius: Alias.BorderRadius.max, y: 4) + } + .padding(.top, Alias.Spacing.large) + Spacer() + } + } + } .tooltipDestination(tooltip: $store.homeCatTooltip.sending(\.setHomeCatTooltip)) .tooltipDestination(tooltip: $store.homeCategoryGuideTooltip.sending(\.setHomeCategoryGuideTooltip)) .tooltipDestination(tooltip: $store.homeTimeGuideTooltip.sending(\.setHomeTimeGuideTooltip)) @@ -141,6 +162,9 @@ public struct HomeView: View { ) { store in MyPageView(store: store) } + .task { + await store.send(.task).finish() + } .onLoad { store.send(.onLoad) } diff --git a/Projects/Feature/SplashFeature/Sources/SplashCore.swift b/Projects/Feature/SplashFeature/Sources/SplashCore.swift index 2fb7d45..97a8f99 100644 --- a/Projects/Feature/SplashFeature/Sources/SplashCore.swift +++ b/Projects/Feature/SplashFeature/Sources/SplashCore.swift @@ -13,6 +13,8 @@ import DatabaseClientInterface import KeychainClientInterface import UserDefaultsClientInterface import AppService +import NetworkTrackingInterface +import DesignSystem import ComposableArchitecture @@ -23,11 +25,16 @@ public struct SplashCore { public init() { } var width: CGFloat = .zero var isLoggedIn: Bool = false + + var dialog: DefaultDialog? } public enum Action: BindableAction { - case onAppear + case task case didFinishInitializeDatabase + case _fetchNetworkConnection(Bool) + case _checkDeviceIDExist + case presentNetworkDialog case moveToHome case moveToOnboarding case binding(BindingAction) @@ -43,6 +50,7 @@ public struct SplashCore { @Dependency(DatabaseClient.self) var databaseClient @Dependency(KeychainClient.self) var keychainClient @Dependency(UserDefaultsClient.self) var userDefaultsClient + @Dependency(NetworkTracking.self) var networkTracking public var body: some ReducerOf { BindingReducer() @@ -51,14 +59,44 @@ public struct SplashCore { private func core(_ state: inout State, _ action: Action) -> EffectOf { switch action { - case .onAppear: + case .task: return .run { send in try await initilizeDatabaseSystem(databaseClient: databaseClient) await send(.didFinishInitializeDatabase) } case.didFinishInitializeDatabase: - return checkDeviceIDExist() + return .run { send in + for await isConnected in networkTracking.updateNetworkConnected() { + await send(._fetchNetworkConnection(isConnected)) + } + } + + case ._fetchNetworkConnection(let isConnected): + if isConnected { + return .run { send in + await send(._checkDeviceIDExist) + } + } else { + return .run { send in + if try await !databaseClient.checkHasTable() { + await send(.presentNetworkDialog) + } else { + await send(._checkDeviceIDExist) + } + } + } + + case ._checkDeviceIDExist: + let deviceID = keychainClient.read(key: deviceIDKey) ?? getDeviceUUID() + return login(deviceID: deviceID) + + case .presentNetworkDialog: + state.dialog = DefaultDialog( + title: "네트워크 연결을 확인해주세요", + firstButton: DialogButtonModel(title: "확인") + ) + return .none case .moveToHome: return .none @@ -73,11 +111,6 @@ public struct SplashCore { } extension SplashCore { - private func checkDeviceIDExist() -> Effect { - let deviceID = keychainClient.read(key: deviceIDKey) ?? getDeviceUUID() - return login(deviceID: deviceID) - } - private func login(deviceID: String) -> Effect { return .run { send in try await authService.login( diff --git a/Projects/Feature/SplashFeature/Sources/SplashView.swift b/Projects/Feature/SplashFeature/Sources/SplashView.swift index eef7b91..b309a9f 100644 --- a/Projects/Feature/SplashFeature/Sources/SplashView.swift +++ b/Projects/Feature/SplashFeature/Sources/SplashView.swift @@ -35,8 +35,9 @@ public struct SplashView: View { guard let frame = value[backgroundFrameID] else { return } store.width = frame.width } - .onAppear { - store.send(.onAppear) + .dialog(dialog: $store.dialog) + .task { + await store.send(.task).finish() } } } diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/16/16_null_primary.imageset/16_null_primary.svg b/Projects/Shared/DesignSystem/Resources/Image.xcassets/16/16_null_primary.imageset/16_null_primary.svg new file mode 100644 index 0000000..0f039ed --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/16/16_null_primary.imageset/16_null_primary.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Projects/Shared/DesignSystem/Resources/Image.xcassets/16/16_null_primary.imageset/Contents.json b/Projects/Shared/DesignSystem/Resources/Image.xcassets/16/16_null_primary.imageset/Contents.json new file mode 100644 index 0000000..ced55ed --- /dev/null +++ b/Projects/Shared/DesignSystem/Resources/Image.xcassets/16/16_null_primary.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "16_null_primary.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}