diff --git a/podcasts/Common SwiftUI/Toast/Toast.swift b/podcasts/Common SwiftUI/Toast/Toast.swift index 5231fe0b65..b6894b34eb 100644 --- a/podcasts/Common SwiftUI/Toast/Toast.swift +++ b/podcasts/Common SwiftUI/Toast/Toast.swift @@ -1,5 +1,9 @@ import Foundation +protocol ToastInsetAdjuster { + var bottomInset: CGFloat { get } +} + /// 🍞 Toast - A lightweight way to display informative overlay messages /// /// Usage: @@ -19,16 +23,18 @@ class Toast { private var window: UIWindow? = nil /// Display the toast message with the given title and actions - static func show(_ title: String, actions: [Action]? = nil, dismissAfter: TimeInterval = 5.0, theme: Style = .defaultTheme) { + static func show(_ title: String, actions: [Action]? = nil, dismissAfter: TimeInterval = 5.0, theme: Style = .defaultTheme, insetAdjuster: ToastInsetAdjuster? = nil) { // Hide any active toasts shared.toastDismissed() guard let scene = SceneHelper.connectedScene() else { return } - let viewModel = ToastViewModel(coordinator: shared, title: title, actions: actions, dismissTime: dismissAfter) + let viewModel = ToastViewModel(coordinator: shared, title: title, actions: actions, dismissTime: dismissAfter, insetAdjuster: insetAdjuster) let view = ToastView(viewModel: viewModel, style: theme) let controller = ThemedHostingController(rootView: view) - + if insetAdjuster == nil { + viewModel.insetAdjuster = controller + } let window = ToastWindow(windowScene: scene, viewModel: viewModel, controller: controller) window.makeKeyAndVisible() @@ -49,6 +55,12 @@ class Toast { } } +extension ThemedHostingController: ToastInsetAdjuster { + var bottomInset: CGFloat { + return view.safeAreaInsets.bottom + } + +} // MARK: - ToastCoordinator extension Toast: ToastDelegate { diff --git a/podcasts/Common SwiftUI/Toast/ToastView.swift b/podcasts/Common SwiftUI/Toast/ToastView.swift index ae5101bca9..9eebba4b79 100644 --- a/podcasts/Common SwiftUI/Toast/ToastView.swift +++ b/podcasts/Common SwiftUI/Toast/ToastView.swift @@ -67,6 +67,7 @@ struct ToastView: View { autoDismiss() } + .ignoresSafeArea() } // MARK: - Views @@ -109,15 +110,17 @@ struct ToastView: View { .shadow(color: .black.opacity(0.3), radius: 10) // Animates the toast in from the bottom of the screen - .offset(y: isVisible ? 0 : contentSize.height) + .offset(y: -(viewModel.insetAdjuster?.bottomInset ?? 0) + (isVisible ? 0 : contentSize.height)) .opacity(isVisible ? 1 : 0) .animation(ToastConstants.animation, value: isVisible) - // Calculate the view size and inform the view model .background(GeometryReader(content: { proxy in Color.clear.onAppear { contentSize = proxy.size - viewModel.updateFrame(proxy.frame(in: .global)) + var rect = proxy.frame(in: .global) + rect.size.height = rect.size.height + (viewModel.insetAdjuster?.bottomInset ?? 0) + rect.origin.y = rect.origin.y - (viewModel.insetAdjuster?.bottomInset ?? 0) + viewModel.updateFrame(rect) } })) diff --git a/podcasts/Common SwiftUI/Toast/ToastViewModel.swift b/podcasts/Common SwiftUI/Toast/ToastViewModel.swift index 1c95955691..3206f697dd 100644 --- a/podcasts/Common SwiftUI/Toast/ToastViewModel.swift +++ b/podcasts/Common SwiftUI/Toast/ToastViewModel.swift @@ -14,6 +14,7 @@ class ToastViewModel: ObservableObject { let title: String let actions: [Toast.Action] let dismissTime: TimeInterval + var insetAdjuster: ToastInsetAdjuster? deinit { autoDismissTimer?.invalidate() @@ -23,11 +24,12 @@ class ToastViewModel: ObservableObject { /// When this is true the view should animate out and call `didDismiss` @Published var didAutoDismiss = false - init(coordinator: ToastDelegate, title: String, actions: [Toast.Action]?, dismissTime: TimeInterval) { + init(coordinator: ToastDelegate, title: String, actions: [Toast.Action]?, dismissTime: TimeInterval, insetAdjuster: ToastInsetAdjuster? = nil) { self.coordinator = coordinator self.title = title self.actions = actions ?? [] self.dismissTime = dismissTime + self.insetAdjuster = insetAdjuster } // MARK: - Window Methods diff --git a/podcasts/Constants.swift b/podcasts/Constants.swift index 19b58b3cff..159cf0e0a9 100644 --- a/podcasts/Constants.swift +++ b/podcasts/Constants.swift @@ -214,6 +214,7 @@ struct Constants { static let sideBarWidthCompact = 88 as CGFloat static let sideBarWidthExpanded = 320 as CGFloat + static let miniPlayerHeight = 70 as CGFloat static let miniPlayerOffset = 80 as CGFloat static let extraShowNotesVerticalSpacing: CGFloat = 60 static let defaultFilterDownloadLimit = 10 as Int32 diff --git a/podcasts/MainTabBarController.swift b/podcasts/MainTabBarController.swift index cf51987554..2208378e0d 100644 --- a/podcasts/MainTabBarController.swift +++ b/podcasts/MainTabBarController.swift @@ -195,7 +195,7 @@ class MainTabBarController: UITabBarController, NavigationProtocol { miniPlayer.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor) ]) - miniPlayer.changeHeightTo(miniPlayer.desiredHeight()) + miniPlayer.changeHeightTo(miniPlayer.desiredHeight) } // MARK: - UITabBarDelegate diff --git a/podcasts/MiniPlayerViewController+Positioning.swift b/podcasts/MiniPlayerViewController+Positioning.swift index 2f53c2b120..4a0d529dd6 100644 --- a/podcasts/MiniPlayerViewController+Positioning.swift +++ b/podcasts/MiniPlayerViewController+Positioning.swift @@ -26,7 +26,7 @@ extension MiniPlayerViewController { // only show if something is playing if PlaybackManager.shared.currentEpisode() == nil { return } - changeHeightTo(desiredHeight()) + changeHeightTo(desiredHeight) moveToHiddenBottomPosition() self.view.isHidden = false view.superview?.layoutIfNeeded() @@ -84,7 +84,7 @@ extension MiniPlayerViewController { } private func moveToHiddenBottomPosition() { - view.transform = CGAffineTransform(translationX: 0, y: desiredHeight()) + view.transform = CGAffineTransform(translationX: 0, y: desiredHeight) view.superview?.layoutIfNeeded() } diff --git a/podcasts/MiniPlayerViewController.swift b/podcasts/MiniPlayerViewController.swift index 03a06e85e8..a383a86884 100644 --- a/podcasts/MiniPlayerViewController.swift +++ b/podcasts/MiniPlayerViewController.swift @@ -81,8 +81,8 @@ class MiniPlayerViewController: SimpleNotificationsViewController { PlaybackManager.shared.skipForward() } - func desiredHeight() -> CGFloat { - 70 + var desiredHeight: CGFloat { + Constants.Values.miniPlayerHeight } func aboutToDisplayFullScreenPlayer() { diff --git a/podcasts/SwiftUI/MiniPlayerPaddingModifier.swift b/podcasts/SwiftUI/MiniPlayerPaddingModifier.swift index 43a6503065..d675e77c15 100644 --- a/podcasts/SwiftUI/MiniPlayerPaddingModifier.swift +++ b/podcasts/SwiftUI/MiniPlayerPaddingModifier.swift @@ -3,11 +3,16 @@ import SwiftUI /// Apply a bottom padding whenever the mini player is visible public struct MiniPlayerSafeAreaInset: ViewModifier { @State var isMiniPlayerVisible: Bool = false + let multipler: CGFloat + + init(multipler: CGFloat) { + self.multipler = multipler + } public func body(content: Content) -> some View { content .safeAreaInset(edge: .bottom, spacing: 0) { - Color.clear.frame(height: Constants.Values.miniPlayerOffset) // Adjust the bottom inset + Color.clear.frame(height: Constants.Values.miniPlayerOffset * multipler) // Adjust the bottom inset } .onAppear { isMiniPlayerVisible = (PlaybackManager.shared.currentEpisode() != nil) @@ -24,7 +29,7 @@ public struct MiniPlayerSafeAreaInset: ViewModifier { // Create an extension for easier usage public extension View { - func miniPlayerSafeAreaInset() -> some View { - self.modifier(MiniPlayerSafeAreaInset()) + func miniPlayerSafeAreaInset(multiplier: CGFloat = 1) -> some View { + self.modifier(MiniPlayerSafeAreaInset(multipler: multiplier)) } } diff --git a/podcasts/UpNextViewController.swift b/podcasts/UpNextViewController.swift index 8288298945..b1fde9c291 100644 --- a/podcasts/UpNextViewController.swift +++ b/podcasts/UpNextViewController.swift @@ -225,7 +225,7 @@ class UpNextViewController: UIViewController, UIGestureRecognizerDelegate { } let upNextShuffleEnabled = Settings.upNextShuffleEnabled() if upNextShuffleEnabled { - Toast.show(L10n.upNextShuffleToastMessage) + Toast.show(L10n.upNextShuffleToastMessage, insetAdjuster: self) } track(.upNextShuffleEnabled, properties: ["value": upNextShuffleEnabled]) } @@ -430,3 +430,13 @@ extension UpNextViewController: AnalyticsSourceProvider { .upNext } } + +extension UpNextViewController: ToastInsetAdjuster { + var bottomInset: CGFloat { + let isMiniPlayerVisible = PlaybackManager.shared.currentEpisode() != nil + let value = view.safeAreaInsets.bottom + (isMiniPlayerVisible && showingInTab ? Constants.Values.miniPlayerHeight : 0) + return value + } + + +}