diff --git a/iOS/Views/Common/MainTabView.swift b/iOS/Views/Common/MainTabView.swift index f0be70e..af1b3dc 100644 --- a/iOS/Views/Common/MainTabView.swift +++ b/iOS/Views/Common/MainTabView.swift @@ -22,7 +22,7 @@ struct MainTabView: View { @State private var debugDragOffset: CGFloat = 0 @State private var showFullConsole = false - // Group toggles -- same @AppStorage keys as DebugConsoleView so state is shared + // Group toggles — same @AppStorage keys as DebugConsoleView so state is shared @AppStorage("dbg_show_iphone") private var dbgShowIPhone = true @AppStorage("dbg_show_watch") private var dbgShowWatch = true @AppStorage("dbg_show_companion") private var dbgShowCompanion = true @@ -91,7 +91,7 @@ struct MainTabView: View { } } - // Debug panel (docked) -- only when enabled and NOT in PiP mode + // Debug panel (docked) — only when enabled and NOT in PiP mode if debugLogger.isEnabled && !debugPipMode { debugPanel .transition(.move(edge: .bottom).combined(with: .opacity)) @@ -140,7 +140,7 @@ struct MainTabView: View { dragOffset = 0 } } else if value.translation.height < -20 { - // Small upward swipe -- open NowPlaying + // Small upward swipe — open NowPlaying dragOffset = 0 withAnimation(.easeInOut(duration: 0.35)) { showNowPlaying = true @@ -157,7 +157,7 @@ struct MainTabView: View { } } - // Now Playing overlay -- always rendered, positioned via offset + // Now Playing overlay — always rendered, positioned via offset // This avoids re-layout animation when appearing if audioPlayer.currentSong != nil { NowPlayingView(isPresented: $showNowPlaying) @@ -166,7 +166,7 @@ struct MainTabView: View { .allowsHitTesting(showNowPlaying) } - // Debug PiP (floating) -- above everything except Now Playing + // Debug PiP (floating) — above everything except Now Playing if debugLogger.isEnabled && debugPipMode { debugPipView .zIndex(showNowPlaying ? 0 : 5) @@ -268,7 +268,7 @@ struct MainTabView: View { Divider().background(Color.white.opacity(0.15)) - // Log entries -- filtered by group toggles + // Log entries — filtered by group toggles ScrollViewReader { proxy in ScrollView { LazyVStack(alignment: .leading, spacing: 1) { @@ -404,7 +404,7 @@ struct MainTabView: View { ) return VStack(spacing: 0) { - // PiP header -- always visible + // PiP header — always visible HStack(spacing: 8) { Image(systemName: "ladybug.fill") .font(.system(size: 10)) @@ -476,9 +476,9 @@ struct MainTabView: View { } ) - // Log content -- hidden when collapsed + // Log content — hidden when collapsed if !pipCollapsed { - // ── Group toggles -- same state as docked panel ─────────────── + // ── Group toggles — same state as docked panel ─────────────── ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 6) { dbgGroupToggle("iPhone", icon: "iphone", isOn: $dbgShowIPhone) @@ -574,7 +574,7 @@ struct MiniPlayerBar: View { var body: some View { let _ = DebugLogger.shared.trackViewBodies ? ViewBodyTracker.shared.record("MiniPlayerBar") : false VStack(spacing: 0) { - // Progress bar extracted into its own view -- only it re-evaluates at 10Hz + // Progress bar extracted into its own view — only it re-evaluates at 10Hz // when audioPlayer.currentTime changes. The rest of MiniPlayerBar body // only re-evaluates on song change, play/pause, or color change. MiniProgressBar( @@ -584,7 +584,7 @@ struct MiniPlayerBar: View { accentPink: accentPink ) ZStack(alignment: .center) { - // Visualizer behind controls -- paused when full NowPlaying is open + // Visualizer behind controls — paused when full NowPlaying is open if VisualizerSettings.shared.enabled && VisualizerSettings.shared.miniPlayerEnabled && !showNowPlaying { CompactVisualizerView( isPlaying: audioPlayer.isPlaying, @@ -694,9 +694,9 @@ struct DynamicIslandView: View { var body: some View { VStack(spacing: 0) { - Spacer().frame(height: 54) // Below status bar + notch + Spacer().frame(height: 12) // Sit directly below Dynamic Island - // The pill -- only this is tappable for NowPlaying + // The pill — only this is tappable for NowPlaying HStack(spacing: 0) { Group { if let song = audioPlayer.currentSong { @@ -744,9 +744,9 @@ struct DynamicIslandView: View { Spacer().frame(width: 10) } - .frame(height: 48) + .frame(height: 44) .background( - RoundedRectangle(cornerRadius: 26, style: .continuous) + RoundedRectangle(cornerRadius: 22, style: .continuous) .fill( LinearGradient( colors: [Color.black, themeColor.opacity(0.25)], @@ -754,10 +754,10 @@ struct DynamicIslandView: View { endPoint: .trailing ) ) - .shadow(color: themeColor.opacity(0.3), radius: 12, y: 2) + .shadow(color: themeColor.opacity(0.4), radius: 8, y: 2) ) - .padding(.horizontal, 32) - .contentShape(RoundedRectangle(cornerRadius: 26)) + .padding(.horizontal, 56) + .contentShape(RoundedRectangle(cornerRadius: 22)) .onTapGesture { withAnimation(.easeInOut(duration: 0.35)) { showNowPlaying = true @@ -777,7 +777,7 @@ struct DynamicIslandView: View { Spacer() } - // Status bar stays visible -- no .statusBarHidden + // Status bar stays visible — no .statusBarHidden } } @@ -785,17 +785,17 @@ struct DynamicIslandView: View { // // Extracted from MiniPlayerBar so only this tiny view re-evaluates when // audioPlayer.currentTime changes (10x/second from the periodic time observer). -// Before this, the ENTIRE MiniPlayerBar body re-evaluated at 10Hz -- including -// the GeometryReader, CompactVisualizerView, all ZStack children, and album art -- +// Before this, the ENTIRE MiniPlayerBar body re-evaluated at 10Hz — including +// the GeometryReader, CompactVisualizerView, all ZStack children, and album art — // causing sustained ~128% CPU even with the visualizer off. // // The key trick: this view declares its OWN @ObservedObject on audioPlayer so // SwiftUI's dependency tracking scopes the 10Hz invalidation to THIS view only. // MiniPlayerBar.body is now only re-evaluated on song change, play/pause, or -// color change -- not on every currentTime tick. +// color change — not on every currentTime tick. private struct MiniProgressBar: View { - // No @ObservedObject on AudioPlayer -- currentTime is no longer @Published. + // No @ObservedObject on AudioPlayer — currentTime is no longer @Published. // TimelineView drives re-evaluation at 10Hz independently so this view never // triggers objectWillChange on AudioPlayer or propagates updates to siblings. @ObservedObject var colorExtractor: AlbumColorExtractor @@ -810,18 +810,18 @@ private struct MiniProgressBar: View { var body: some View { let _ = DebugLogger.shared.trackViewBodies ? ViewBodyTracker.shared.record("MiniProgressBar") : false // GeometryReader and gesture live OUTSIDE TimelineView. - // Previously the gesture was inside the TimelineView closure -- every 0.1s + // Previously the gesture was inside the TimelineView closure — every 0.1s // TimelineView re-evaluated its content, reconstructing the DragGesture // and interrupting in-progress touches, causing missed/dropped scrubs. // Only the fill Rectangle that reads currentTime sits inside TimelineView. GeometryReader { geo in ZStack(alignment: .leading) { - // Track background -- static, no TimelineView needed + // Track background — static, no TimelineView needed Rectangle() .fill(Color.white.opacity(0.1)) .frame(height: isScrubbing ? 8 : 3) - // Fill -- inside TimelineView so it updates at 10Hz without + // Fill — inside TimelineView so it updates at 10Hz without // disturbing the gesture recognizer above TimelineView(.periodic(from: .now, by: 0.1)) { _ in let player = AudioPlayer.shared @@ -836,7 +836,7 @@ private struct MiniProgressBar: View { height: isScrubbing ? 8 : 3) } - // Scrub thumb -- only visible while dragging + // Scrub thumb — only visible while dragging if isScrubbing { TimelineView(.periodic(from: .now, by: 0.1)) { _ in let player = AudioPlayer.shared @@ -854,7 +854,7 @@ private struct MiniProgressBar: View { } .frame(maxHeight: .infinity) .contentShape(Rectangle()) - // Gesture is stable -- never reconstructed by TimelineView ticks + // Gesture is stable — never reconstructed by TimelineView ticks .gesture( DragGesture(minimumDistance: 0) .onChanged { value in @@ -884,7 +884,7 @@ func dismissKeyboard() { } /// UIKit tap recognizer that dismisses keyboard without blocking other touches. -/// Added once to the key window -- fires alongside all gestures. +/// Added once to the key window — fires alongside all gestures. private class KeyboardDismissTapRecognizer: UITapGestureRecognizer, UIGestureRecognizerDelegate { override init(target: Any?, action: Selector?) { super.init(target: target, action: action) @@ -902,7 +902,7 @@ private class KeyboardDismissTapRecognizer: UITapGestureRecognizer, UIGestureRec } } -/// Helper target for the tap gesture -- calls endEditing on the window +/// Helper target for the tap gesture — calls endEditing on the window private class KeyboardDismissTarget: NSObject { @objc func dismiss() { dismissKeyboard()