Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ struct MainFeature {
@Presents var alert: AlertState<Never>?
var unreadPushCount = 0
var isObservingUnreadPushCount = false
var isWide = false
var windowControlsInset = CGFloat.zero
}

enum Action: Equatable {
enum Action: BindableAction, Equatable {
case alert(PresentationAction<Never>)
case binding(BindingAction<State>)
case view(ViewAction)
case store(StoreAction)

Expand All @@ -46,10 +49,13 @@ struct MainFeature {
@Dependency(\.setApplicationBadgeCount) var setApplicationBadgeCount

var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .alert:
break
case .binding:
break
case .view(.onAppear):
guard !state.isObservingUnreadPushCount else { break }
state.isObservingUnreadPushCount = true
Expand Down
52 changes: 43 additions & 9 deletions Application/Presentation/Entry/Sources/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct MainView: View {
@Binding var selectedTab: MainTab
@State private var store: StoreOf<MainFeature>
private let windowEvent: TodoEditorWindowEvent
private let sideBarMinimumWidth = CGFloat(800)

init(
windowEvent: TodoEditorWindowEvent,
Expand All @@ -31,6 +32,9 @@ struct MainView: View {
}

var body: some View {
let showsSideBar = isiOSAppOnMac || horizontalSizeClass == .regular && store.isWide
let tabContentInset = horizontalSizeClass == .compact ? store.windowControlsInset : 0

HStack(spacing: 0) {
if showsSideBar {
SideBar(
Expand All @@ -54,6 +58,7 @@ struct MainView: View {
tab: .home,
isSelected: selectedTab == .home,
showsSideBar: showsSideBar,
windowControlsInset: tabContentInset,
windowEvent: windowEvent
)
}
Expand All @@ -66,6 +71,7 @@ struct MainView: View {
tab: .today,
isSelected: selectedTab == .today,
showsSideBar: showsSideBar,
windowControlsInset: tabContentInset,
windowEvent: windowEvent
)
}
Expand All @@ -78,6 +84,7 @@ struct MainView: View {
tab: .notification,
isSelected: selectedTab == .notification,
showsSideBar: showsSideBar,
windowControlsInset: tabContentInset,
windowEvent: windowEvent
)
}
Expand All @@ -91,34 +98,67 @@ struct MainView: View {
tab: .profile,
isSelected: selectedTab == .profile,
showsSideBar: showsSideBar,
windowControlsInset: tabContentInset,
windowEvent: windowEvent
)
}
}
}
.onGeometryChange(for: Bool.self) { proxy in
sideBarMinimumWidth <= proxy.size.width
} action: { isWide in
store.send(.binding(.set(\.isWide, isWide)))
}
.onGeometryChange(for: CGFloat.self) { proxy in
proxy.windowControlsInset
} action: { inset in
store.send(.binding(.set(\.windowControlsInset, inset)))
}
.toastHost()
.onAppear { store.send(.view(.onAppear)) }
.onChange(of: selectedTab, initial: true) { _, tab in
store.send(.view(.selectedTabChanged(tab)))
}
.prominentAlert(store, state: \.alert, action: \.alert)
}
}

// toolbarVisibility 적용 전 첫 프레임의 탭 바 노출 방지
private struct TabBarVisibility: UIViewControllerRepresentable {
let isHidden: Bool

func makeUIViewController(context: Context) -> Controller {
Controller()
}

private var showsSideBar: Bool {
isiOSAppOnMac || horizontalSizeClass == .regular
func updateUIViewController(_ controller: Controller, context: Context) {
controller.isTabBarHidden = isHidden
controller.tabBarController?.isTabBarHidden = isHidden
}

final class Controller: UIViewController {
var isTabBarHidden = false

override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
tabBarController?.isTabBarHidden = isTabBarHidden
}
}
}

private struct MainTabContent: View {
let tab: MainTab
let isSelected: Bool
let showsSideBar: Bool
let windowControlsInset: CGFloat
let windowEvent: TodoEditorWindowEvent

var body: some View {
content
.environment(\.isTabContentActive, isSelected)
.environment(\.windowControlsInset, windowControlsInset)
.toolbarVisibility(showsSideBar ? .hidden : .visible, for: .tabBar)
.background { TabBarVisibility(isHidden: showsSideBar) }
}

@ViewBuilder
Expand Down Expand Up @@ -165,19 +205,13 @@ private struct SideBar: View {
}
.padding(.horizontal, 12)
.padding(.vertical, 16)
.padding(.top, windowControlTopPadding(in: geometry))
.padding(.top, geometry.windowControlsInset)
}
.background {
Color.surface
.ignoresSafeArea(.container, edges: .vertical)
}
}

private func windowControlTopPadding(in geometry: GeometryProxy) -> CGFloat {
guard #available(iOS 26.0, *),
UIDevice.current.userInterfaceIdiom == .pad else { return 0 }
return geometry.containerCornerInsets.topLeading.height
}
}

private struct SideBarButton: View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct HomeFeature {
var isTodoCategoryExpanded = false
var isNetworkConnected = true
var selectedTodoCategory: TodoCategory?
var searchInset = CGFloat.zero
var loading = LoadingFeature.State()

var showContentPicker: Bool {
Expand Down
12 changes: 11 additions & 1 deletion Application/Presentation/HomeTab/Sources/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import PresentationShared
public struct HomeView: View {
@Environment(\.openWindow) private var openWindow
@Environment(\.isiOSAppOnMac) private var isiOSAppOnMac
@Environment(\.windowControlsInset) private var windowControlsInset
@State private var router = NavigationRouter<HomeRoute>()
@State private var searchStore: StoreOf<SearchFeature>
@State private var store: StoreOf<HomeFeature>
Expand Down Expand Up @@ -116,6 +117,7 @@ public struct HomeView: View {
HomeCreateButton(store: store)
}
.padding(.horizontal, 16)
.padding(.top, windowControlsInset)
.padding(.bottom, 8)
.background(Color.appBackground)
.toolbarBackground(Color.appBackground)
Expand All @@ -134,6 +136,7 @@ public struct HomeView: View {
GoalCreateView()
case .goalDetail(let goalID):
GoalDetailView(goalId: goalID)
.environment(\.windowControlsInset, 0)
}
}

Expand All @@ -148,6 +151,12 @@ public struct HomeView: View {
}
case .search:
SearchView(store: searchStore)
.environment(\.windowControlsInset, store.searchInset)
.onGeometryChange(for: CGFloat.self) { proxy in
proxy.windowControlsInset
} action: { inset in
store.send(.binding(.set(\.searchInset, inset)))
}
}
}

Expand Down Expand Up @@ -277,7 +286,8 @@ private struct HomeTodoSection: View {

TodoCategoryGridLayout(
columnCount: columnCount,
itemWidth: iconSize
itemWidth: iconSize,
maximumRowSpacing: dynamicTypeSize.isAccessibilitySize ? .infinity : iconSize / 2
) {
ForEach(visibleItems) { item in
HomeTodoCategoryLink(item: item, iconSize: iconSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ import SwiftUI
struct TodoCategoryGridLayout: Layout {
let columnCount: Int
let itemWidth: CGFloat
let maximumRowSpacing: CGFloat

func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) -> CGSize {
let width = proposal.width ?? itemWidth * CGFloat(columnCount)
let spacing = spacing(for: width)
let rowSpacing = min(spacing(for: width), maximumRowSpacing)
let rowHeights = rowHeights(subviews: subviews)
let rowSpacing = spacing * CGFloat(max(0, rowHeights.count - 1))
let totalRowSpacing = rowSpacing * CGFloat(max(0, rowHeights.count - 1))

return CGSize(
width: width,
height: spacing + rowHeights.reduce(0, +) + rowSpacing
height: rowSpacing + rowHeights.reduce(0, +) + totalRowSpacing
)
}

Expand All @@ -34,8 +35,9 @@ struct TodoCategoryGridLayout: Layout {
cache: inout ()
) {
let spacing = spacing(for: bounds.width)
let rowSpacing = min(spacing, maximumRowSpacing)
let rowHeights = rowHeights(subviews: subviews)
var currentY = bounds.minY + spacing
var currentY = bounds.minY + rowSpacing

for rowIndex in rowHeights.indices {
let rowStartIndex = rowIndex * columnCount
Expand All @@ -54,7 +56,7 @@ struct TodoCategoryGridLayout: Layout {
)
}

currentY += rowHeights[rowIndex] + spacing
currentY += rowHeights[rowIndex] + rowSpacing
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PresentationShared

struct SearchView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.windowControlsInset) private var windowControlsInset
@State private var router = NavigationRouter<Path>()
@State var store: StoreOf<SearchFeature>

Expand Down Expand Up @@ -75,6 +76,7 @@ struct SearchView: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.padding(.top, windowControlsInset)
.padding(.bottom, 8)
.background(Color.appBackground, ignoresSafeAreaEdges: .top)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ private struct TodoListTopBar: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.openWindow) private var openWindow
@Environment(\.isiOSAppOnMac) private var isiOSAppOnMac
@Environment(\.windowControlsInset) private var windowControlsInset
@Namespace private var searchTransition
let store: StoreOf<TodoListFeature>

Expand Down Expand Up @@ -247,6 +248,7 @@ private struct TodoListTopBar: View {

TodoListFilterHeader(store: store)
}
.padding(.top, windowControlsInset)
.padding(.bottom, 12)
.background(Color.appBackground, ignoresSafeAreaEdges: .top)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public struct PushNotificationListView: View {
})
.id(sheetStore.todoId)
}
.environment(\.windowControlsInset, 0)
.background(Color.appBackground)
.presentationDragIndicator(.visible)
}
Expand All @@ -82,6 +83,7 @@ public struct PushNotificationListView: View {
}

private struct PushNotificationListContent: View {
@Environment(\.windowControlsInset) private var windowControlsInset
@ScaledMetric(relativeTo: .headline) private var iconSize = 36
let store: StoreOf<PushNotificationListFeature>

Expand Down Expand Up @@ -217,6 +219,7 @@ private struct PushNotificationListContent: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
.padding(.top, windowControlsInset)
.padding(.bottom, 8)
.background(Color.appBackground)
.toolbarBackground(Color.appBackground)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public extension EnvironmentValues {
self[IOSAppOnMacKey.self]
}

var windowControlsInset: CGFloat {
get { self[WindowControlsInsetKey.self] }
set { self[WindowControlsInsetKey.self] = newValue }
}

private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
Expand Down Expand Up @@ -52,6 +57,10 @@ public extension EnvironmentValues {
}
}

private struct WindowControlsInsetKey: EnvironmentKey {
static let defaultValue = CGFloat.zero
}

private struct IOSAppOnMacKey: EnvironmentKey {
static var defaultValue: Bool {
ProcessInfo.processInfo.isiOSAppOnMac
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// GeometryProxy+.swift
// PresentationShared
//
// Created by opfic on 11/4/26.
//

import SwiftUI

public extension GeometryProxy {
var windowControlsInset: CGFloat {
guard #available(iOS 26.0, *),
UIDevice.current.userInterfaceIdiom == .pad else { return 0 }
return containerCornerInsets.topLeading.height
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public struct TodoDetailView: View {
@Environment(\.isTabContentActive) private var isTabContentActive
@Environment(\.openWindow) private var openWindow
@Environment(\.isiOSAppOnMac) private var isiOSAppOnMac
@Environment(\.windowControlsInset) private var windowControlsInset
@State var store: StoreOf<TodoDetailFeature>
private let windowEvent: TodoEditorWindowEvent?
private let onBack: (() -> Void)?
Expand Down Expand Up @@ -89,6 +90,7 @@ public struct TodoDetailView: View {
}
.padding(.horizontal)
.padding(.vertical, 12)
.padding(.top, windowControlsInset)
.background(Color.appBackground, ignoresSafeAreaEdges: .top)
}

Expand Down Expand Up @@ -169,6 +171,7 @@ public struct TodoDetailView: View {
TodoDetailView(store: todoStore)
}
}
.environment(\.windowControlsInset, 0)
.background(Color.appBackground)
.presentationDragIndicator(.visible)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public struct TodoEditorFeature {
public var tagText: String = ""
public var tabViewTag: EditorTab = .editor
public var editorHeaderHeight = CGFloat.zero
public var windowControlsInset = CGFloat.zero
public var categories: [TodoCategoryItem] = []
public var category = TodoCategoryItem(from: .system(.etc))
public var selectedCategoryID: String {
Expand Down
Loading
Loading