From aa0a9ef282427be17a24d43764bc8861141c12da Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 30 Apr 2026 20:45:57 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20Today=20=EC=9C=84=EC=A0=AF=EC=9D=84?= =?UTF-8?q?=20=ED=83=AD=ED=96=88=EC=9D=84=20=EC=8B=9C=20TodayTodoView?= =?UTF-8?q?=EB=A1=9C=20=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/App/Routing/MainTab.swift | 28 ++++++++++++++++++++++++ DevLog/UI/Common/MainView.swift | 11 +++++++++- DevLogWidget/Today/TodayTodoWidget.swift | 1 + WidgetShared/WidgetDeepLink.swift | 26 ++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 DevLog/App/Routing/MainTab.swift create mode 100644 WidgetShared/WidgetDeepLink.swift 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..9c50fd00 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 + @State private var selectedTab = MainTab.home 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,10 +68,15 @@ struct MainView: View { Image(systemName: "person.crop.circle.fill") Text(String(localized: "nav_profile")) } + .tag(MainTab.profile) } .onAppear { viewModel.send(.onAppear) } + .onOpenURL { url in + guard let mainTab = MainTab(widgetURL: url) else { return } + selectedTab = mainTab + } .alert( viewModel.state.alertTitle, isPresented: Binding( 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..db1d600b --- /dev/null +++ b/WidgetShared/WidgetDeepLink.swift @@ -0,0 +1,26 @@ +// +// 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 { + URL(string: "\(scheme)://\(host)")! + } +} From 30dfb9001161bf3c702a89fdec251926eda6bfaa Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 30 Apr 2026 20:46:18 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=ED=9E=88=ED=8A=B8=EB=A7=B5=20?= =?UTF-8?q?=EC=9C=84=EC=A0=AF=EC=9D=84=20=ED=83=AD=ED=96=88=EC=9D=84=20?= =?UTF-8?q?=EC=8B=9C=20ProfileView=EB=A1=9C=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=90=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/HeatmapWidget.swift | 1 + 1 file changed, 1 insertion(+) 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("활동 히트맵을 표시합니다.") From 8db2db2c9e6ef2491e572398c7ff3a1edb2b78ad Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 30 Apr 2026 22:24:17 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20=EC=9C=84=EC=A0=AF?= =?UTF-8?q?=EC=9D=84=20=ED=83=AD=ED=95=B4=20=EC=95=B1=EC=97=90=20=EB=93=A4?= =?UTF-8?q?=EC=96=B4=EA=B0=84=20=ED=9B=84=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=95=98=EB=A9=B4=20=ED=95=B4=EB=8B=B9=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=ED=83=AD=EC=9C=BC=EB=A1=9C=20=EB=93=A4=EC=96=B4=EA=B0=80?= =?UTF-8?q?=EC=A7=80=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/App/RootView.swift | 23 ++++++++++++++++++++--- DevLog/UI/Common/MainView.swift | 6 +----- 2 files changed, 21 insertions(+), 8 deletions(-) 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/UI/Common/MainView.swift b/DevLog/UI/Common/MainView.swift index 9c50fd00..c0d717ae 100644 --- a/DevLog/UI/Common/MainView.swift +++ b/DevLog/UI/Common/MainView.swift @@ -10,7 +10,7 @@ import SwiftUI struct MainView: View { @Environment(\.diContainer) var container: DIContainer @State var viewModel: MainViewModel - @State private var selectedTab = MainTab.home + @Binding var selectedTab: MainTab var body: some View { TabView(selection: $selectedTab) { @@ -73,10 +73,6 @@ struct MainView: View { .onAppear { viewModel.send(.onAppear) } - .onOpenURL { url in - guard let mainTab = MainTab(widgetURL: url) else { return } - selectedTab = mainTab - } .alert( viewModel.state.alertTitle, isPresented: Binding( From e0721f061c6bbdca17831347060a84d6eacd6ccd Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 30 Apr 2026 23:09:20 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=EA=B0=95=EC=A0=9C=20=EC=98=B5?= =?UTF-8?q?=EC=85=94=EB=84=90=20=EC=96=B8=EB=9E=98=ED=95=91=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WidgetShared/WidgetDeepLink.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/WidgetShared/WidgetDeepLink.swift b/WidgetShared/WidgetDeepLink.swift index db1d600b..26ce00b5 100644 --- a/WidgetShared/WidgetDeepLink.swift +++ b/WidgetShared/WidgetDeepLink.swift @@ -12,15 +12,18 @@ enum WidgetDeepLink { static let todayTodoHost = "today" static let heatmapHost = "profile" - static var todayTodoURL: URL { + static var todayTodoURL: URL? { url(host: todayTodoHost) } - static var heatmapURL: URL { + static var heatmapURL: URL? { url(host: heatmapHost) } - private static func url(host: String) -> URL { - URL(string: "\(scheme)://\(host)")! + private static func url(host: String) -> URL? { + var urlComponents = URLComponents() + urlComponents.scheme = scheme + urlComponents.host = host + return urlComponents.url } }