diff --git a/DevLog/App/RootView.swift b/DevLog/App/RootView.swift index f2e8f4ed..f201ad0c 100644 --- a/DevLog/App/RootView.swift +++ b/DevLog/App/RootView.swift @@ -11,15 +11,19 @@ struct RootView: View { @Environment(\.diContainer) var container: DIContainer @State var viewModel: RootViewModel @State private var selectedRoute: AppRoute? + @State private var selectedMainTab = MainTab.home var body: some View { ZStack { Color(UIColor.systemGroupedBackground).ignoresSafeArea() if let signIn = viewModel.state.signIn { if signIn { - MainView(viewModel: MainViewModel( - unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self) - )) + MainView( + viewModel: MainViewModel( + unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self) + ), + selectedTab: $selectedMainTab + ) } else { LoginView(viewModel: LoginViewModel( signInUseCase: container.resolve(SignInUseCase.self)) @@ -29,6 +33,19 @@ struct RootView: View { } .preferredColorScheme(viewModel.state.theme.colorScheme) .onAppear { viewModel.send(.onAppear) } + .onChange(of: viewModel.state.signIn) { _, value in + guard value == false else { return } + selectedMainTab = .home + } + .onOpenURL { url in + guard let mainTab = MainTab(widgetURL: url) else { return } + switch viewModel.state.signIn { + case .some(false): + selectedMainTab = .home + case .some(true), .none: + selectedMainTab = mainTab + } + } .alert(viewModel.state.alertTitle, isPresented: Binding( get: { viewModel.state.showAlert }, set: { viewModel.send(.setAlert($0)) } diff --git a/DevLog/App/Routing/MainTab.swift b/DevLog/App/Routing/MainTab.swift new file mode 100644 index 00000000..96c9e525 --- /dev/null +++ b/DevLog/App/Routing/MainTab.swift @@ -0,0 +1,28 @@ +// +// MainTab.swift +// DevLog +// +// Created by opfic on 4/30/26. +// + +import Foundation + +enum MainTab: Hashable { + case home + case today + case notification + case profile + + init?(widgetURL: URL) { + guard widgetURL.scheme?.lowercased() == WidgetDeepLink.scheme.lowercased() else { return nil } + + switch widgetURL.host { + case WidgetDeepLink.todayTodoHost: + self = .today + case WidgetDeepLink.heatmapHost: + self = .profile + default: + return nil + } + } +} diff --git a/DevLog/UI/Common/MainView.swift b/DevLog/UI/Common/MainView.swift index b527bd99..c0d717ae 100644 --- a/DevLog/UI/Common/MainView.swift +++ b/DevLog/UI/Common/MainView.swift @@ -10,9 +10,10 @@ import SwiftUI struct MainView: View { @Environment(\.diContainer) var container: DIContainer @State var viewModel: MainViewModel + @Binding var selectedTab: MainTab var body: some View { - TabView { + TabView(selection: $selectedTab) { HomeView(viewModel: HomeViewModel( fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), updatePreferencesUseCase: container.resolve(UpdateTodoCategoryPreferencesUseCase.self), @@ -28,6 +29,7 @@ struct MainView: View { Image(systemName: "house.fill") Text(String(localized: "nav_home")) } + .tag(MainTab.home) TodayView(viewModel: TodayViewModel( fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self), @@ -39,6 +41,7 @@ struct MainView: View { Image(systemName: "sun.max.fill") Text(String(localized: "nav_today")) } + .tag(MainTab.today) PushNotificationListView(viewModel: PushNotificationListViewModel( fetchUseCase: container.resolve(FetchPushNotificationsUseCase.self), deleteUseCase: container.resolve(DeletePushNotificationUseCase.self), @@ -52,6 +55,7 @@ struct MainView: View { Text(String(localized: "nav_notifications")) } .badge(viewModel.state.unreadPushCount) + .tag(MainTab.notification) ProfileView(viewModel: ProfileViewModel( fetchUserDataUseCase: container.resolve(FetchUserDataUseCase.self), fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), @@ -64,6 +68,7 @@ struct MainView: View { Image(systemName: "person.crop.circle.fill") Text(String(localized: "nav_profile")) } + .tag(MainTab.profile) } .onAppear { viewModel.send(.onAppear) diff --git a/DevLogWidget/Heatmap/HeatmapWidget.swift b/DevLogWidget/Heatmap/HeatmapWidget.swift index 3f028493..0561e4d4 100644 --- a/DevLogWidget/Heatmap/HeatmapWidget.swift +++ b/DevLogWidget/Heatmap/HeatmapWidget.swift @@ -20,6 +20,7 @@ struct HeatmapWidget: Widget { ) { entry in HeatmapWidgetEntryView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) + .widgetURL(WidgetDeepLink.heatmapURL) } .configurationDisplayName("Heatmap") .description("활동 히트맵을 표시합니다.") diff --git a/DevLogWidget/Today/TodayTodoWidget.swift b/DevLogWidget/Today/TodayTodoWidget.swift index 67d3a204..9e0a7c67 100644 --- a/DevLogWidget/Today/TodayTodoWidget.swift +++ b/DevLogWidget/Today/TodayTodoWidget.swift @@ -20,6 +20,7 @@ struct TodayTodoWidget: Widget { ) { entry in TodayTodoWidgetEntryView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) + .widgetURL(WidgetDeepLink.todayTodoURL) } .description("오늘 기준 Todo 목록을 표시합니다.") .configurationDisplayName("Today") diff --git a/WidgetShared/WidgetDeepLink.swift b/WidgetShared/WidgetDeepLink.swift new file mode 100644 index 00000000..26ce00b5 --- /dev/null +++ b/WidgetShared/WidgetDeepLink.swift @@ -0,0 +1,29 @@ +// +// WidgetDeepLink.swift +// DevLog +// +// Created by opfic on 4/30/26. +// + +import Foundation + +enum WidgetDeepLink { + static let scheme = "DevLog" + static let todayTodoHost = "today" + static let heatmapHost = "profile" + + static var todayTodoURL: URL? { + url(host: todayTodoHost) + } + + static var heatmapURL: URL? { + url(host: heatmapHost) + } + + private static func url(host: String) -> URL? { + var urlComponents = URLComponents() + urlComponents.scheme = scheme + urlComponents.host = host + return urlComponents.url + } +}