diff --git a/TheNoiseClock/Views/Clock/ClockView.swift b/TheNoiseClock/Views/Clock/ClockView.swift index ac2f6d1..521dd5a 100644 --- a/TheNoiseClock/Views/Clock/ClockView.swift +++ b/TheNoiseClock/Views/Clock/ClockView.swift @@ -22,104 +22,43 @@ struct ClockView: View { GeometryReader { geometry in ZStack { - // Time display - fills entire available space - TimeDisplayView( - date: viewModel.currentTime, - use24Hour: viewModel.style.use24Hour, - showSeconds: viewModel.style.showSeconds, - digitColor: viewModel.style.digitColor, - glowIntensity: viewModel.style.glowIntensity, - manualScale: viewModel.style.digitScale, - stretched: viewModel.style.stretched, - showAmPmBadge: viewModel.style.showAmPmBadge, - clockOpacity: viewModel.style.clockOpacity, - fontFamily: viewModel.style.fontFamily, - fontWeight: viewModel.style.fontWeight, - fontDesign: viewModel.style.fontDesign + // Main clock display container + ClockDisplayContainer( + currentTime: viewModel.currentTime, + style: viewModel.style, + isDisplayMode: viewModel.isDisplayMode ) - .transition(.opacity) - // Top overlay - positioned at top with safe area consideration - VStack { - if viewModel.style.showBattery || viewModel.style.showDate { - TopOverlayView( - showBattery: viewModel.style.showBattery, - showDate: viewModel.style.showDate, - color: viewModel.style.digitColor.opacity(UIConstants.Opacity.primary), - opacity: viewModel.style.overlayOpacity - ) - .padding(.top, UIConstants.Spacing.small) - .padding(.horizontal, UIConstants.Spacing.large) - .transition(.opacity) - } - Spacer() - } + // Top overlay container + ClockOverlayContainer(style: viewModel.style) } - .scaleEffect(viewModel.isDisplayMode ? 1.0 : 0.995) - .animation(UIConstants.AnimationCurves.smooth, value: viewModel.isDisplayMode) } } .ignoresSafeArea(.all, edges: viewModel.isDisplayMode ? .bottom : []) - .navigationTitle(viewModel.isDisplayMode ? "" : "Clock") - .toolbar { - if !viewModel.isDisplayMode { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - showSettings = true - } label: { - Image(systemName: "gear") - .font(.title2) - .transition(.opacity) - } - } - } - } - .navigationBarBackButtonHidden(viewModel.isDisplayMode) - .toolbar(viewModel.isDisplayMode ? .hidden : .automatic) .statusBarHidden(viewModel.isDisplayMode) - .onAppear { - setTabBarHidden(viewModel.isDisplayMode, animated: false) - } - .onDisappear { - setTabBarHidden(false, animated: false) - } .sheet(isPresented: $showSettings) { ClockSettingsView(style: viewModel.style) { newStyle in viewModel.updateStyle(newStyle) } .presentationDetents([.medium, .large]) } - .contentShape(Rectangle()) - .simultaneousGesture( - LongPressGesture(minimumDuration: AppConstants.DisplayMode.longPressDuration) - .onEnded { _ in - viewModel.toggleDisplayMode() - setTabBarHidden(viewModel.isDisplayMode, animated: true) - // Status bar hiding is handled by the .statusBarHidden modifier - } - ) - } - - // MARK: - Private Methods - private func setTabBarHidden(_ hidden: Bool, animated: Bool) { - // This will be handled by the View extension - // For now, we'll keep the UIKit implementation - #if canImport(UIKit) - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first, - let tabBarController = window.rootViewController?.findTabBarController() else { return } - - let tabBar = tabBarController.tabBar - let changes = { - tabBar.alpha = hidden ? 0 : 1 + .overlay { + // Toolbar overlay + ClockToolbar( + isDisplayMode: viewModel.isDisplayMode, + onSettingsTap: { showSettings = true } + ) } - if animated { - UIView.animate(withDuration: AppConstants.AnimationDurations.short, animations: changes) - } else { - changes() + .overlay { + // Tab bar management overlay + ClockTabBarManager(isDisplayMode: viewModel.isDisplayMode, animated: true) + } + .overlay { + // Gesture handling overlay + ClockGestureHandler { + viewModel.toggleDisplayMode() + } } - tabBar.isUserInteractionEnabled = !hidden - #endif } } diff --git a/TheNoiseClock/Views/Clock/Components/ClockDisplayContainer.swift b/TheNoiseClock/Views/Clock/Components/ClockDisplayContainer.swift new file mode 100644 index 0000000..2ad15b1 --- /dev/null +++ b/TheNoiseClock/Views/Clock/Components/ClockDisplayContainer.swift @@ -0,0 +1,52 @@ +// +// ClockDisplayContainer.swift +// TheNoiseClock +// +// Created by Matt Bruce on 9/8/25. +// + +import SwiftUI + +/// Container component that handles the main clock display layout and scaling +struct ClockDisplayContainer: View { + + // MARK: - Properties + let currentTime: Date + let style: ClockStyle + let isDisplayMode: Bool + + // MARK: - Body + var body: some View { + GeometryReader { geometry in + ZStack { + // Time display - fills entire available space + TimeDisplayView( + date: currentTime, + use24Hour: style.use24Hour, + showSeconds: style.showSeconds, + digitColor: style.digitColor, + glowIntensity: style.glowIntensity, + manualScale: style.digitScale, + stretched: style.stretched, + showAmPmBadge: style.showAmPmBadge, + clockOpacity: style.clockOpacity, + fontFamily: style.fontFamily, + fontWeight: style.fontWeight, + fontDesign: style.fontDesign + ) + .transition(.opacity) + } + .scaleEffect(isDisplayMode ? 1.0 : 0.995) + .animation(UIConstants.AnimationCurves.smooth, value: isDisplayMode) + } + } +} + +// MARK: - Preview +#Preview { + ClockDisplayContainer( + currentTime: Date(), + style: ClockStyle(), + isDisplayMode: false + ) +} diff --git a/TheNoiseClock/Views/Clock/Components/ClockGestureHandler.swift b/TheNoiseClock/Views/Clock/Components/ClockGestureHandler.swift new file mode 100644 index 0000000..17f3df0 --- /dev/null +++ b/TheNoiseClock/Views/Clock/Components/ClockGestureHandler.swift @@ -0,0 +1,32 @@ +// +// ClockGestureHandler.swift +// TheNoiseClock +// +// Created by Matt Bruce on 9/8/25. +// + +import SwiftUI + +/// Component that handles gesture interactions for the clock view +struct ClockGestureHandler: View { + + // MARK: - Properties + let onLongPress: () -> Void + + // MARK: - Body + var body: some View { + EmptyView() + .contentShape(Rectangle()) + .simultaneousGesture( + LongPressGesture(minimumDuration: AppConstants.DisplayMode.longPressDuration) + .onEnded { _ in + onLongPress() + } + ) + } +} + +// MARK: - Preview +#Preview { + ClockGestureHandler(onLongPress: {}) +} diff --git a/TheNoiseClock/Views/Clock/Components/ClockOverlayContainer.swift b/TheNoiseClock/Views/Clock/Components/ClockOverlayContainer.swift new file mode 100644 index 0000000..b328c1e --- /dev/null +++ b/TheNoiseClock/Views/Clock/Components/ClockOverlayContainer.swift @@ -0,0 +1,38 @@ +// +// ClockOverlayContainer.swift +// TheNoiseClock +// +// Created by Matt Bruce on 9/8/25. +// + +import SwiftUI + +/// Container component that handles the positioning and display of top overlays +struct ClockOverlayContainer: View { + + // MARK: - Properties + let style: ClockStyle + + // MARK: - Body + var body: some View { + VStack { + if style.showBattery || style.showDate { + TopOverlayView( + showBattery: style.showBattery, + showDate: style.showDate, + color: style.digitColor.opacity(UIConstants.Opacity.primary), + opacity: style.overlayOpacity + ) + .padding(.top, UIConstants.Spacing.small) + .padding(.horizontal, UIConstants.Spacing.large) + .transition(.opacity) + } + Spacer() + } + } +} + +// MARK: - Preview +#Preview { + ClockOverlayContainer(style: ClockStyle()) +} diff --git a/TheNoiseClock/Views/Clock/Components/ClockTabBarManager.swift b/TheNoiseClock/Views/Clock/Components/ClockTabBarManager.swift new file mode 100644 index 0000000..b016112 --- /dev/null +++ b/TheNoiseClock/Views/Clock/Components/ClockTabBarManager.swift @@ -0,0 +1,55 @@ +// +// ClockTabBarManager.swift +// TheNoiseClock +// +// Created by Matt Bruce on 9/8/25. +// + +import SwiftUI + +/// Component that manages tab bar visibility for display mode +struct ClockTabBarManager: View { + + // MARK: - Properties + let isDisplayMode: Bool + let animated: Bool + + // MARK: - Body + var body: some View { + EmptyView() + .onAppear { + setTabBarHidden(isDisplayMode, animated: false) + } + .onDisappear { + setTabBarHidden(false, animated: false) + } + .onChange(of: isDisplayMode) { _, newValue in + setTabBarHidden(newValue, animated: animated) + } + } + + // MARK: - Private Methods + private func setTabBarHidden(_ hidden: Bool, animated: Bool) { + #if canImport(UIKit) + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first, + let tabBarController = window.rootViewController?.findTabBarController() else { return } + + let tabBar = tabBarController.tabBar + let changes = { + tabBar.alpha = hidden ? 0 : 1 + } + if animated { + UIView.animate(withDuration: AppConstants.AnimationDurations.short, animations: changes) + } else { + changes() + } + tabBar.isUserInteractionEnabled = !hidden + #endif + } +} + +// MARK: - Preview +#Preview { + ClockTabBarManager(isDisplayMode: false, animated: false) +} diff --git a/TheNoiseClock/Views/Clock/Components/ClockToolbar.swift b/TheNoiseClock/Views/Clock/Components/ClockToolbar.swift new file mode 100644 index 0000000..fa14680 --- /dev/null +++ b/TheNoiseClock/Views/Clock/Components/ClockToolbar.swift @@ -0,0 +1,47 @@ +// +// ClockToolbar.swift +// TheNoiseClock +// +// Created by Matt Bruce on 9/8/25. +// + +import SwiftUI + +/// Component that handles the clock view toolbar and navigation +struct ClockToolbar: View { + + // MARK: - Properties + let isDisplayMode: Bool + let onSettingsTap: () -> Void + + // MARK: - Body + var body: some View { + EmptyView() + .navigationTitle(isDisplayMode ? "" : "Clock") + .toolbar { + if !isDisplayMode { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + onSettingsTap() + } label: { + Image(systemName: "gear") + .font(.title2) + .transition(.opacity) + } + } + } + } + .navigationBarBackButtonHidden(isDisplayMode) + .toolbar(isDisplayMode ? .hidden : .automatic) + } +} + +// MARK: - Preview +#Preview { + NavigationStack { + ClockToolbar( + isDisplayMode: false, + onSettingsTap: {} + ) + } +}