-
-
Notifications
You must be signed in to change notification settings - Fork 245
Mac bottom padding #1448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mac bottom padding #1448
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a bug where the mini player was blocking content on iPad and MacOS by dynamically calculating the mini player's height and using it to add appropriate bottom insets to tab contents.
Changes:
- Added dynamic height calculation for the mini player using GeometryReader and PreferenceKey
- Changed mini player positioning from
safeAreaInsettooverlaywith environment-based inset propagation - Added unused
playerHeightandplayerHeightBindingproperties toPlayerState
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| BookPlayer/Utils/PlayerState.swift | Adds playerHeight property and binding (unused) |
| BookPlayer/Utils/Extensions/View+BookPlayer.swift | Refactors mini player positioning to use overlay with dynamic height calculation and environment-based inset propagation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .overlay(alignment: .bottom) { | ||
| regular() | ||
| .fixedSize(horizontal: false, vertical: true) | ||
| .overlay( | ||
| GeometryReader { geo in | ||
| Color.clear.preference( | ||
| key: MiniPlayerBottomInsetPreferenceKey.self, | ||
| value: geo.size.height | ||
| ) | ||
| } | ||
| ) | ||
| } |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change from safeAreaInset(edge: .bottom, spacing: 0, content: regular) to overlay(alignment: .bottom) fundamentally changes how the mini player is positioned. With the old approach, the mini player was placed in the safe area which automatically pushed content up. With the new approach, the mini player is overlaid on top of content, and you're relying on MiniPlayerSafeAreaInsetModifier to separately push content up via the miniPlayerBottomInset environment value.
This approach is more fragile because:
- It creates a dependency between two separate modifiers that must stay in sync
- Views that don't use
.miniPlayerSafeAreaInset()will have content obscured by the overlay - There could be timing issues if the preference change fires after layout
If this change is necessary to work around a platform-specific issue with safeAreaInset on iPad/MacOS, consider adding a comment explaining why the overlay approach is needed and what the tradeoffs are.
| .onPreferenceChange(MiniPlayerBottomInsetPreferenceKey.self) { | ||
| let topPadding: CGFloat = 20 | ||
| let reduceSize = hSize == .compact ? 44.0 : 0 | ||
| let reduceTopPadding = $0 > reduceSize ? reduceSize : 0 |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The calculation logic here contains magic numbers (20, 44.0) and unclear variable names that make it difficult to understand the intent. Consider adding comments explaining:
- Why topPadding is 20
- Why reduceSize is 44.0 for compact size classes
- What the overall formula is trying to achieve (i.e., why we add topPadding then subtract reduceTopPadding)
Additionally, the condition $0 > reduceSize ? reduceSize : 0 means reduceTopPadding is either 44 or 0, which seems like it should be documented.
| .onPreferenceChange(MiniPlayerBottomInsetPreferenceKey.self) { | |
| let topPadding: CGFloat = 20 | |
| let reduceSize = hSize == .compact ? 44.0 : 0 | |
| let reduceTopPadding = $0 > reduceSize ? reduceSize : 0 | |
| .onPreferenceChange(MiniPlayerBottomInsetPreferenceKey.self) { | |
| // Extra spacing between the mini player and the safe area / tab bar. | |
| // This value was chosen to match the visual design of the mini player. | |
| let topPadding: CGFloat = 20 | |
| // Amount to reduce the effective bottom inset when the horizontal size class is compact. | |
| // 44.0 corresponds to the standard tab bar height in compact environments; in regular | |
| // size classes we do not need this reduction. | |
| let reduceSize = hSize == .compact ? 44.0 : 0 | |
| // When the mini player height exceeds the reduction threshold, subtract the full | |
| // reduction amount (44 or 0). This ensures the mini player does not "double count" | |
| // the tab bar height and keeps the layout visually aligned. | |
| let reduceTopPadding = $0 > reduceSize ? reduceSize : 0 | |
| // Final inset = measured mini player height + design padding - any compact-size reduction. |
| if #available(iOS 26.1, *) { | ||
| defaultBody(content) | ||
| } else if #available(iOS 26.0, *) { | ||
| content | ||
| .tabBarMinimizeBehavior(.onScrollDown) | ||
| .tabViewBottomAccessory(content: accessory) | ||
| } else { | ||
| defaultBody(content) | ||
| } |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation detected. The if/else if/else branches should align at the same indentation level (2 spaces from the function body), but lines 228, 231, and 232-233 use extra indentation. This deviates from the codebase's consistent 2-space indentation style.
| content | ||
| .safeAreaInset(edge: .bottom, spacing: 0, content: regular) | ||
| } | ||
| .overlay(alignment: .bottom) { | ||
| regular() | ||
| .fixedSize(horizontal: false, vertical: true) | ||
| .overlay( | ||
| GeometryReader { geo in | ||
| Color.clear.preference( | ||
| key: MiniPlayerBottomInsetPreferenceKey.self, | ||
| value: geo.size.height | ||
| ) | ||
| } | ||
| ) | ||
| } | ||
| .onPreferenceChange(MiniPlayerBottomInsetPreferenceKey.self) { | ||
| let topPadding: CGFloat = 20 | ||
| let reduceSize = hSize == .compact ? 44.0 : 0 | ||
| let reduceTopPadding = $0 > reduceSize ? reduceSize : 0 | ||
| miniPlayerBottomInset = $0 + topPadding - reduceTopPadding | ||
| } | ||
| .environment(\.miniPlayerBottomInset, miniPlayerBottomInset) | ||
| } |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation detected. The content inside this function should follow the codebase's 2-space indentation style, but line 241 starts with extra indentation. Lines 242-260 continue with inconsistent indentation that doesn't match the codebase convention.
4e87f3a to
0538353
Compare
0538353 to
de33a3f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let miniPlayerHeight = $0.height | ||
| let topPadding: CGFloat = 20 | ||
| let reduceSize = hSize == .compact ? 44.0 : 0 | ||
| let reduceTopPadding = miniPlayerHeight > reduceSize ? reduceSize : 0 | ||
| miniPlayerBottomInset = miniPlayerHeight + topPadding - reduceTopPadding |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
topPadding (20), reduceSize (44), and the derived reduceTopPadding introduce magic-number layout behavior that’s hard to reason about and keep consistent with MiniPlayerView padding/tab bar sizing. Consider deriving these values from existing layout constants/safe-area metrics (or centralizing them as named constants with a brief rationale) so the inset logic remains correct across devices.
| extension EnvironmentValues { | ||
| var miniPlayerBottomInset: CGFloat { | ||
| get { self[MiniPlayerBottomInsetKey.self] } | ||
| set { self[MiniPlayerBottomInsetKey.self] = newValue } | ||
| } |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newly added EnvironmentValues key uses 4-space indentation, while the rest of this file consistently uses 2-space indentation. Please reformat this block to match the established style for readability and to reduce noisy diffs going forward.
| Color.clear.preference( | ||
| key: MiniPlayerSizePreferenceKey.self, | ||
| value: geo.size | ||
| ) |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GeometryReader/Color.clear overlay used to emit the mini-player size is hit-testable by default and can end up intercepting taps meant for the mini player’s buttons/gestures. Make the measuring overlay non-interactive (e.g., disable hit testing on the overlay) or move the measurement into a background that won’t block interaction.
| Color.clear.preference( | |
| key: MiniPlayerSizePreferenceKey.self, | |
| value: geo.size | |
| ) | |
| Color.clear | |
| .allowsHitTesting(false) | |
| ..preference( | |
| key: MiniPlayerSizePreferenceKey.self, | |
| value: geo.size | |
| ) |
ff93822 to
4ac7559
Compare
4ac7559 to
a2f122f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| key: MiniPlayerSizePreferenceKey.self, | ||
| value: geo.size | ||
| ) | ||
| } |
Copilot
AI
Feb 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GeometryReader/Color.clear overlay used for size measurement will sit on top of regular() and can intercept taps/gestures, which may break mini player interactions. Ensure the measuring overlay does not participate in hit-testing (e.g., disable hit testing on the overlay view).
| } | |
| } | |
| .allowsHitTesting(false) |
| private struct MiniPlayerBottomInsetKey: EnvironmentKey { | ||
| static let defaultValue: CGFloat = 80 | ||
| } | ||
|
|
||
| extension EnvironmentValues { | ||
| var miniPlayerBottomInset: CGFloat { | ||
| get { self[MiniPlayerBottomInsetKey.self] } | ||
| set { self[MiniPlayerBottomInsetKey.self] = newValue } | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Toolbar utils | ||
| struct MiniPlayerSizePreferenceKey: PreferenceKey { | ||
| static var defaultValue: CGSize = .zero | ||
| static func reduce(value: inout CGSize, nextValue: () -> CGSize) { | ||
| value = nextValue() | ||
| } | ||
| } | ||
|
|
||
| struct MiniPlayerModifier<Regular: View, Accessory: View>: ViewModifier { | ||
| @ViewBuilder let regular: () -> Regular | ||
| @ViewBuilder let accessory: () -> Accessory | ||
|
|
||
|
|
||
| @Environment(\.horizontalSizeClass) var hSize | ||
| @State private var miniPlayerBottomInset: CGFloat = 80 | ||
|
|
Copilot
AI
Feb 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bottom inset defaults are duplicated as hard-coded 80 values (MiniPlayerBottomInsetKey.defaultValue and MiniPlayerModifier.miniPlayerBottomInset initial state), and additional constants like topPadding = 20 are unexplained. Extract these into a single named constant (or derive from safe-area/tab bar metrics) to avoid drift and make the intent clearer.
Bugfix
Related tasks
Approach
Things to be aware of / Things to focus on
Screenshots