From 5786a0210a0cd515364333c3ca82f9ece6bdb521 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sun, 27 Sep 2026 22:09:22 +0200 Subject: [PATCH] Use the macOS capsule mini player on iOS 18 and drop its duplicate close button The iOS 18 overlay layout appended a hardcoded close button after the preset buttons, and the default mini player preset already includes Close, so the bar showed two X buttons. Replace the full-width material bar with the same 420pt capsule macOS uses (shared capsuleLayout), and only add the fallback close button when the active preset has none. Inset the iOS 18 overlay 16pt horizontally; clear the bottom tab bar in compact width and float 12pt above the edge in regular width, where sidebarAdaptable keeps the tabs at the top. --- Yattee/ContentView.swift | 5 +- Yattee/Views/Player/MiniPlayerView.swift | 79 +++++++----------------- 2 files changed, 26 insertions(+), 58 deletions(-) diff --git a/Yattee/ContentView.swift b/Yattee/ContentView.swift index 9c7e4c3b..42d8c10a 100644 --- a/Yattee/ContentView.swift +++ b/Yattee/ContentView.swift @@ -289,7 +289,10 @@ struct iOS18AdaptiveTabView: View { Spacer() MiniPlayerView() } - .padding(.bottom, 49) + .padding(.horizontal, 16) + // Compact width has a bottom tab bar (49pt) to clear; regular width + // (sidebarAdaptable) keeps its tabs at the top, so only float above the edge. + .padding(.bottom, horizontalSizeClass == .compact ? 49 + 8 : 12) // Use move-only transition (no opacity) to prevent thumbnail flash during collapse // The opacity transition caused video to appear faded during player sheet dismiss .transition(.move(edge: .bottom)) diff --git a/Yattee/Views/Player/MiniPlayerView.swift b/Yattee/Views/Player/MiniPlayerView.swift index 50aa06e1..631b2d6a 100644 --- a/Yattee/Views/Player/MiniPlayerView.swift +++ b/Yattee/Views/Player/MiniPlayerView.swift @@ -132,12 +132,12 @@ struct MiniPlayerView: View { var body: some View { Group { #if os(macOS) - macOSCapsuleLayout + capsuleLayout #else if isTabAccessory { accessoryLayout } else { - overlayLayout + capsuleLayout } #endif } @@ -213,65 +213,25 @@ struct MiniPlayerView: View { .padding(.vertical, 8) } - // MARK: - Overlay Layout + // MARK: - Capsule Layout (macOS + iOS 18 overlay) - private var overlayLayout: some View { - HStack(spacing: 12) { - // Video preview - tap for PiP (or expand if PiP unavailable) - videoPreviewView - .frame(width: 60, height: 34) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .contentShape(Rectangle()) - .onTapGesture { - handleVideoPreviewTap() - } - - // Title/author area - tap to expand player (restores from PiP first if needed) - VStack(alignment: .leading, spacing: 2) { - MarqueeText( - text: displayTitle, - font: .subheadline, - fontWeight: .medium, - foregroundStyle: .primary - ) - - authorLine(font: .caption) - } - .contentShape(Rectangle()) - .onTapGesture { - expandPlayerWithPiPRestore() - } - - Spacer() - .contentShape(Rectangle()) - .onTapGesture { - expandPlayerWithPiPRestore() - } - - // Dynamic buttons from settings - buttonsView - - // Always show close button in overlay layout - closeButton - } - .padding(.horizontal) - .padding(.vertical, 8) - .background(.ultraThinMaterial) - .overlay(alignment: .bottom) { - progressLine - } + /// Preview size for the capsule: slightly larger on iOS for touch. + private var capsulePreviewSize: CGSize { + #if os(macOS) + CGSize(width: 44, height: 26) + #else + CGSize(width: 56, height: 32) + #endif } - // MARK: - macOS Capsule Layout - - #if os(macOS) - /// Compact, centered capsule shown in the main window on macOS (Music.app style). - /// The expanded player lives in a separate window, so this stays visible alongside it. - private var macOSCapsuleLayout: some View { + /// Compact, centered capsule (Music.app style). On macOS it floats in the main + /// window alongside the separate player window; on iOS 18 (no tab accessory) + /// it floats above the tab bar with the same width limit. + private var capsuleLayout: some View { HStack(spacing: 10) { // Video preview - tap for PiP (or expand if PiP unavailable) videoPreviewView - .frame(width: 44, height: 26) + .frame(width: capsulePreviewSize.width, height: capsulePreviewSize.height) .clipShape(RoundedRectangle(cornerRadius: 4)) .contentShape(Rectangle()) .onTapGesture { @@ -289,13 +249,19 @@ struct MiniPlayerView: View { authorLine(font: .caption) } + .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) .onTapGesture { expandPlayerWithPiPRestore() } - // Dynamic buttons from settings (includes close by default - no separate close button) + // Dynamic buttons from settings (close is part of the preset) buttonsView + + // Safety net: keep a way to close when the preset has no close button + if !miniPlayerSettings.buttons.contains(where: { $0.buttonType == .close }) { + closeButton + } } .padding(.horizontal, 12) .padding(.vertical, 6) @@ -307,7 +273,6 @@ struct MiniPlayerView: View { .clipShape(Capsule()) .shadow(color: .black.opacity(0.25), radius: 8, y: 4) } - #endif // MARK: - Shared Components