Remove dead code found by Periphery on all platforms

Delete declarations that Periphery reports unused on every platform that
compiles them (iOS, macOS, tvOS), including nine views used only by their
own previews. Platform-gated code still used on another platform, device-
only MPV render paths, migration models, CloudKit record names, App
Intents metadata and unused parameters are kept.
This commit is contained in:
Arkadiusz Fal
2026-10-08 23:57:56 +02:00
parent 45e19e6536
commit 4770bbe3b9
141 changed files with 14 additions and 4191 deletions

View File

@@ -13,8 +13,6 @@ import SwiftUI
/// Automatically handles DeArrow integration.
/// Supports optional queue context for auto-play functionality.
struct BookmarkRowView: View {
@Environment(\.appEnvironment) private var appEnvironment
let bookmark: Bookmark
var style: VideoRowStyle = .regular
var watchProgress: Double? = nil
@@ -27,10 +25,6 @@ struct BookmarkRowView: View {
var videoIndex: Int? = nil
var loadMoreVideos: LoadMoreVideosCallback? = nil
private var accentColor: Color {
appEnvironment?.settingsManager.resolvedAccentColor ?? .accentColor
}
private var isBookmarkValid: Bool {
bookmark.modelContext != nil && !bookmark.isDeleted
}

View File

@@ -27,14 +27,6 @@ struct ExpandedCommentsView: View {
@State private var scrollBounceOffset: CGFloat = 0
@State private var isDismissThresholdReached = false
private var playerState: PlayerState? { appEnvironment?.playerService.state }
private var contentService: ContentService? { appEnvironment?.contentService }
private var instancesManager: InstancesManager? { appEnvironment?.instancesManager }
private var comments: [Comment] { playerState?.comments ?? [] }
private var commentsState: CommentsLoadState { playerState?.commentsState ?? .idle }
private var videoSource: ContentSource? { playerState?.currentVideo?.id.source }
private var backgroundColor: Color {
#if os(iOS)
Color(.systemBackground)
@@ -363,53 +355,6 @@ struct CommentsListContent: View {
}
}
// MARK: - Scroll Fade Overlay
/// A view modifier that applies a gradient fade overlay to the top and bottom edges of a scroll view.
private struct ScrollFadeOverlayModifier: ViewModifier {
let fadeHeight: CGFloat
func body(content: Content) -> some View {
content
.overlay(alignment: .top) {
// Top gradient fade
LinearGradient(
colors: [backgroundColor, .clear],
startPoint: .top,
endPoint: .bottom
)
.frame(height: fadeHeight)
.allowsHitTesting(false)
}
.overlay(alignment: .bottom) {
// Bottom gradient fade
LinearGradient(
colors: [.clear, backgroundColor],
startPoint: .top,
endPoint: .bottom
)
.frame(height: fadeHeight)
.allowsHitTesting(false)
}
}
private var backgroundColor: Color {
#if os(iOS)
Color(.systemBackground)
#elseif os(macOS)
Color(nsColor: .windowBackgroundColor)
#else
Color.black
#endif
}
}
private extension View {
func scrollFadeMask(fadeHeight: CGFloat = 24) -> some View {
modifier(ScrollFadeOverlayModifier(fadeHeight: fadeHeight))
}
}
// MARK: - Scroll Detection
#if os(iOS)

View File

@@ -83,52 +83,6 @@ struct ErrorStateView: View {
}
}
// MARK: - Load State View (Generic Container)
/// A generic container that handles loading, error, and content states.
///
/// Example usage:
/// ```swift
/// LoadStateView(
/// isLoading: viewModel.isLoading,
/// errorMessage: viewModel.error,
/// data: viewModel.data,
/// loadingMessage: "Loading...",
/// errorTitle: "Error",
/// onRetry: { await viewModel.load() }
/// ) { data in
/// ContentView(data: data)
/// }
/// ```
struct LoadStateView<Content: View, Data>: View {
let isLoading: Bool
let errorMessage: String?
let data: Data?
var loadingMessage: String? = nil
var loadingSubtext: String? = nil
var errorTitle: String = "Error"
var onRetry: (() async -> Void)? = nil
var onDismiss: (() -> Void)? = nil
@ViewBuilder let content: (Data) -> Content
var body: some View {
Group {
if isLoading && data == nil {
LoadingView(message: loadingMessage, subtext: loadingSubtext)
} else if let error = errorMessage, data == nil {
ErrorStateView(
title: errorTitle,
message: error,
onRetry: onRetry,
onDismiss: onDismiss
)
} else if let data {
content(data)
}
}
}
}
// MARK: - Previews
#Preview("Loading") {
@@ -143,38 +97,3 @@ struct LoadStateView<Content: View, Data>: View {
onDismiss: {}
)
}
#Preview("LoadStateView - Loading") {
LoadStateView(
isLoading: true,
errorMessage: nil,
data: nil as String?,
loadingMessage: "Fetching data..."
) { data in
Text(data)
}
}
#Preview("LoadStateView - Error") {
LoadStateView(
isLoading: false,
errorMessage: "Network error occurred",
data: nil as String?,
errorTitle: "Failed to Load",
onRetry: {}
) { data in
Text(data)
}
}
#Preview("LoadStateView - Content") {
LoadStateView(
isLoading: false,
errorMessage: nil,
data: "Hello, World!",
loadingMessage: "Loading..."
) { data in
Text(data)
.font(.largeTitle)
}
}

View File

@@ -24,34 +24,4 @@ struct ShareSheet: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
}
#elseif os(macOS)
struct ShareSheet: View {
let items: [Any]
var body: some View {
VStack(spacing: 12) {
if let text = items.first as? String {
ScrollView {
Text(text)
.font(.caption)
.fontDesign(.monospaced)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
.frame(maxHeight: 400)
}
Button(String(localized: "settings.advanced.logs.export.copy")) {
if let text = items.first as? String {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(text, forType: .string)
}
}
.keyboardShortcut(.defaultAction)
}
.padding()
.frame(minWidth: 420, minHeight: 320)
}
}
#endif

View File

@@ -1,144 +0,0 @@
//
// SourceListContainer.swift
// Yattee
//
// Container for source list views with inset/plain styling.
//
import SwiftUI
/// A container that handles inset/plain list styling for source views.
///
/// Simplified version of VideoListContainer for simpler source rows.
struct SourceListContainer<Header: View, Content: View>: View {
let listStyle: VideoListStyle
@ViewBuilder let header: () -> Header
@ViewBuilder let content: () -> Content
/// Corner radius for card container in inset style.
private let cardCornerRadius: CGFloat = 10
/// Horizontal padding for card container.
private let cardHorizontalPadding: CGFloat = 16
/// Bottom padding for card container.
private let cardBottomPadding: CGFloat = 16
var body: some View {
#if os(tvOS)
// tvOS: Simple ScrollView without background overlay
ScrollView {
LazyVStack(spacing: 0) {
header()
content()
}
}
#else
// iOS/macOS: Background overlay pattern
backgroundStyle.color
.ignoresSafeArea()
.overlay(
ScrollView {
LazyVStack(spacing: 0) {
header()
if listStyle == .inset {
insetCardContent
} else {
plainContent
}
}
}
)
#endif
}
// MARK: - Private
private var backgroundStyle: ListBackgroundStyle {
listStyle == .inset ? .grouped : .plain
}
private var insetCardContent: some View {
LazyVStack(spacing: 0) {
content()
}
.background(ListBackgroundStyle.card.color)
.clipShape(RoundedRectangle(cornerRadius: cardCornerRadius))
.padding(.horizontal, cardHorizontalPadding)
.padding(.bottom, cardBottomPadding)
}
private var plainContent: some View {
LazyVStack(spacing: 0) {
content()
}
.padding(.bottom, cardBottomPadding)
}
}
// MARK: - Convenience Initializers
extension SourceListContainer where Header == EmptyView {
/// Creates a source list container without a header.
init(
listStyle: VideoListStyle,
@ViewBuilder content: @escaping () -> Content
) {
self.listStyle = listStyle
self.header = { EmptyView() }
self.content = content
}
}
// MARK: - Preview
#Preview("Inset Style") {
SourceListContainer(listStyle: .inset) {
Text("Header")
.padding()
} content: {
ForEach(0..<5) { index in
SourceListRow(isLast: index == 4, listStyle: .inset) {
HStack(spacing: 12) {
Image(systemName: "server.rack")
.font(.title2)
.foregroundStyle(.blue)
.frame(width: 32)
VStack(alignment: .leading) {
Text("Source \(index + 1)")
.font(.headline)
Text("example.com")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
}
}
}
}
#Preview("Plain Style") {
SourceListContainer(listStyle: .plain) {
ForEach(0..<5) { index in
SourceListRow(isLast: index == 4, listStyle: .plain) {
HStack(spacing: 12) {
Image(systemName: "server.rack")
.font(.title2)
.foregroundStyle(.blue)
.frame(width: 32)
VStack(alignment: .leading) {
Text("Source \(index + 1)")
.font(.headline)
Text("example.com")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
}
}
}
}

View File

@@ -76,7 +76,6 @@ struct SwipeActionModifier: ViewModifier {
@State private var progress: CGFloat = 0
// Scroll tracking for auto-close on scroll
@State private var currentScrollOffset: CGFloat = 0
@State private var storedScrollOffset: CGFloat?
// Shared state reference (computed to avoid inclusion in memberwise init)

View File

@@ -1,236 +0,0 @@
//
// TextTruncationEffect.swift
// Yattee
//
// Animated text truncation effect with "...more" indicator.
// Adapted from Balaji Venkatesh's TruncationEffect implementation.
//
import SwiftUI
#if os(macOS)
import AppKit
#else
import UIKit
#endif
extension Text {
/// Applies an animated truncation effect that shows "...more" when collapsed.
/// - Parameters:
/// - length: Number of lines to show when truncated
/// - isEnabled: Whether truncation is enabled (false = expanded)
/// - animation: Animation to use for expand/collapse transitions
@ViewBuilder
func truncationEffect(length: Int, isEnabled: Bool, animation: Animation) -> some View {
self.modifier(
TruncationEffectViewModifier(
length: length,
isEnabled: isEnabled,
animation: animation
)
)
}
}
// MARK: - View Modifier
private struct TruncationEffectViewModifier: ViewModifier {
var length: Int
var isEnabled: Bool
var animation: Animation
@State private var limitedSize: CGSize = .zero
@State private var fullSize: CGSize = .zero
@State private var animatedProgress: CGFloat = 0
func body(content: Content) -> some View {
content
.lineLimit(length)
.opacity(0)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
.onGeometryChange(for: CGSize.self) {
$0.size
} action: { newValue in
limitedSize = newValue
}
.frame(height: isExpanded ? fullSize.height : nil)
.overlay {
// Full content with animation
GeometryReader { proxy in
let contentSize = proxy.size
content
.textRenderer(
TruncationTextRenderer(
length: length,
progress: animatedProgress
)
)
.fixedSize(horizontal: false, vertical: true)
.onGeometryChange(for: CGSize.self) {
$0.size
} action: { newValue in
fullSize = newValue
}
.frame(
width: contentSize.width,
height: contentSize.height,
alignment: isExpanded ? .leading : .topLeading
)
}
}
.contentShape(.rect)
.onChange(of: isEnabled) { _, newValue in
withAnimation(animation) {
animatedProgress = !newValue ? 1 : 0
}
}
.onAppear {
// Set initial value without animation
animatedProgress = !isEnabled ? 1 : 0
}
}
var isExpanded: Bool {
animatedProgress == 1
}
}
// MARK: - Text Renderer
@Animatable
private struct TruncationTextRenderer: TextRenderer {
@AnimatableIgnored var length: Int
var progress: CGFloat
/// Minimum number of hidden characters to show "...more" indicator.
/// If less than this, just show the full text without truncation indicator.
private let minHiddenCharacters = 10
func draw(layout: Text.Layout, in ctx: inout GraphicsContext) {
let totalLines = layout.count
let hasExtraLines = totalLines > length
// Count characters in lines beyond the limit
let hiddenCharacterCount: Int = {
guard hasExtraLines else { return 0 }
var count = 0
for index in length..<totalLines {
count += layout[index].flatMap { $0 }.count
}
return count
}()
// If hidden content is too short, don't show "...more" - just draw normally
let shouldShowMoreIndicator = hasExtraLines && hiddenCharacterCount >= minHiddenCharacters
for (index, line) in layout.enumerated() {
var copyContext = ctx
if index == length - 1 && shouldShowMoreIndicator {
drawMoreTextAtEnd(line: line, context: &copyContext)
} else {
if index < length {
// Draw all visible lines
copyContext.draw(line)
} else if shouldShowMoreIndicator {
drawLinesWithBlurEffect(index: index, layout: layout, in: &copyContext)
} else {
// No "...more" indicator - just draw with fade based on progress
copyContext.opacity = progress
copyContext.draw(line)
}
}
}
}
private func drawLinesWithBlurEffect(index: Int, layout: Text.Layout, in ctx: inout GraphicsContext) {
let line = layout[index]
let lineIndex = Double(index - length)
let totalExtraLines = Double(layout.count - length)
// Divide the animation progress among all extra lines
let lineStartProgress = lineIndex / max(1, totalExtraLines)
let lineEndProgress = (lineIndex + 1) / max(1, totalExtraLines)
// Calculate this specific line's progress
let lineProgress = max(0, min(1, (progress - lineStartProgress) / (lineEndProgress - lineStartProgress)))
ctx.opacity = lineProgress
ctx.addFilter(.blur(radius: blurRadius - (blurRadius * lineProgress)))
ctx.draw(line)
}
private func drawMoreTextAtEnd(line: Text.Layout.Element, context: inout GraphicsContext) {
let runs = line.flatMap { $0 }
let runsCount = runs.count
let text = " ..." + String(localized: "common.more")
let textCount = text.count
// Draw runs until the text count
for index in 0..<max(runsCount - textCount, 0) {
let run = runs[index]
context.draw(run)
}
// Draw remaining runs with opacity filter
for index in max(runsCount - textCount, 0)..<runsCount {
let run = runs[index]
context.opacity = progress
context.draw(run)
}
// Draw "...more" text
let textRunIndex = max(runsCount - textCount, 0)
var typography: Text.Layout.TypographicBounds
if !runs.isEmpty {
typography = runs[textRunIndex].typographicBounds
} else {
typography = line.typographicBounds
}
let fontSize: CGFloat = typography.ascent
let font = platformFont(ofSize: fontSize)
let spacing: CGFloat = textWidth(text, font: font) / 2
let swiftUIText = Text(text)
.font(Font(font))
.foregroundStyle(.secondary)
let origin = CGPoint(
x: typography.rect.minX + spacing,
y: typography.rect.midY
)
context.opacity = 1 - progress
context.draw(swiftUIText, at: origin)
}
private var blurRadius: CGFloat {
5
}
// MARK: - Cross-Platform Font Helpers
#if os(macOS)
private func platformFont(ofSize size: CGFloat) -> NSFont {
NSFont.systemFont(ofSize: size)
}
private func textWidth(_ text: String, font: NSFont) -> CGFloat {
let attributes: [NSAttributedString.Key: Any] = [.font: font]
return (text as NSString).size(withAttributes: attributes).width
}
#else
private func platformFont(ofSize size: CGFloat) -> UIFont {
UIFont.systemFont(ofSize: size)
}
private func textWidth(_ text: String, font: UIFont) -> CGFloat {
let attributes: [NSAttributedString.Key: Any] = [.font: font]
return (text as NSString).size(withAttributes: attributes).width
}
#endif
}

View File

@@ -138,22 +138,6 @@ extension VideoListContainer where Footer == EmptyView {
}
}
extension VideoListContainer where Header == EmptyView {
/// Creates a video list container with a footer but no header.
init(
listStyle: VideoListStyle,
rowStyle: VideoRowStyle,
@ViewBuilder content: @escaping () -> Content,
@ViewBuilder footer: @escaping () -> Footer
) {
self.listStyle = listStyle
self.rowStyle = rowStyle
self.header = { EmptyView() }
self.content = content
self.footer = footer
}
}
// MARK: - Preview
#Preview("Inset Style") {

View File

@@ -1,77 +0,0 @@
//
// VideoNavigationButton.swift
// Yattee
//
// Navigation button for switching between videos in VideoInfoView.
//
import SwiftUI
/// A floating circular button for navigating between videos in a queue.
/// Used in VideoInfoView to switch to previous/next video.
struct VideoNavigationButton: View {
let direction: Direction
let action: () -> Void
var isLoading: Bool = false
var hasError: Bool = false
enum Direction {
case previous
case next
var icon: String {
switch self {
case .previous: return "chevron.left"
case .next: return "chevron.right"
}
}
var accessibilityLabel: String {
switch self {
case .previous: return String(localized: "video.navigation.previous")
case .next: return String(localized: "video.navigation.next")
}
}
}
var body: some View {
Button(action: action) {
ZStack {
if isLoading {
ProgressView()
.controlSize(.small)
.tint(.primary)
} else {
Image(systemName: direction.icon)
.font(.title2)
.fontWeight(.semibold)
}
}
.foregroundStyle(.primary)
.frame(width: 44, height: 44)
.glassBackground(.regular, in: .circle)
.shadow(color: .black.opacity(0.1), radius: 8, y: 4)
.overlay {
if hasError {
Circle()
.stroke(Color.red, lineWidth: 2)
}
}
}
.buttonStyle(.plain)
.disabled(isLoading)
.accessibilityLabel(direction.accessibilityLabel)
}
}
// MARK: - Preview
#Preview("Previous") {
VideoNavigationButton(direction: .previous) {}
.padding()
}
#Preview("Next") {
VideoNavigationButton(direction: .next) {}
.padding()
}

View File

@@ -11,7 +11,6 @@ import SwiftUI
/// View modifier that applies user-configurable swipe actions plus fixed context-specific actions.
struct VideoSwipeActionsModifier: ViewModifier {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var queueContext
@Environment(\.presentAppSheet) private var presentAppSheet
let video: Video

View File

@@ -12,7 +12,6 @@ import SwiftUI
/// Shows layout picker (list/grid), size options, and filter toggles.
/// Reusable across different views with their own storage bindings.
struct ViewOptionsSheet: View {
@Environment(\.appEnvironment) private var appEnvironment
@Binding var layout: VideoListLayout
@Binding var rowStyle: VideoRowStyle
@Binding var gridColumns: Int