Three changes to DynamicIslandView:
• Top spacer 54 → 12 — pulls the pill up to sit directly under the Dynamic Island cutout • Horizontal padding 32 → 56 — narrows the pill from both sides • Height 48 → 44 — slightly more compact to match the island’s proportions If it’s sitting too high or too low after testing, the spacer value is the one knob to turn — increase it if it overlaps the island, decrease if there’s too much gap.
This commit is contained in:
parent
f19d21e4cc
commit
8eaab0bc93
1 changed files with 30 additions and 30 deletions
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue