Files
yattee/Shared/Home/HomeView.swift
Arkadiusz Fal f4d4daccd0 Optimize SwiftUI performance throughout the app
This commit addresses multiple SwiftUI performance bottlenecks identified
through code analysis, focusing on view rendering efficiency, list
performance, and memory usage optimization.

Key improvements:

- HomeView: Optimize async task management using structured concurrency
  with async let to handle multiple Defaults updates in a single task

- VideoCell: Remove GeometryReader from VideoCellThumbnail to eliminate
  layout thrashing; change @ObservedObject to computed property for shared
  ThumbnailsModel

- ThumbnailView: Cache URL extension computation in init() instead of
  recalculating on every body evaluation

- FavoriteItemView: Replace filter().prefix() with early-exit loop and
  capacity reservation for significant performance gain on large lists

- ContentItemView: Optimize FetchRequest creation with direct predicate
  construction only for video items, empty predicate for others

- VideoPlayerView: Fix playerSize didSet trigger by moving
  updateSidebarQueue() calls to explicit onChange/onAppear handlers

- FeedView: Replace .unique() with Set-based deduplication for O(n)
  performance and reduced allocations

- VerticalCells: Remove expensive sorting on every redraw; items should
  be pre-sorted from source

These optimizations follow SwiftUI best practices by minimizing expensive
computations in view bodies, caching computed values, using efficient data
structures, and avoiding unnecessary redraws and layout passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:14:35 +01:00

244 lines
8.2 KiB
Swift

import Defaults
import Siesta
import SwiftUI
import UniformTypeIdentifiers
struct HomeView: View {
@ObservedObject private var accounts = AccountsModel.shared
@ObservedObject private var player = PlayerModel.shared
@State private var presentingHomeSettings = false
@State private var favoritesChanged = false
@State private var updateTask: Task<Void, Never>?
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
var watches: FetchedResults<Watch>
@State private var historyID = UUID()
#if os(iOS)
@State private var recentDocumentsID = UUID()
#endif
#if !os(tvOS)
@Default(.favorites) private var favorites
@Default(.widgetsSettings) private var widgetsSettings
#endif
@Default(.showFavoritesInHome) private var showFavoritesInHome
@Default(.showOpenActionsInHome) private var showOpenActionsInHome
@Default(.showQueueInHome) private var showQueueInHome
private var navigation: NavigationModel { .shared }
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack {
#if !os(tvOS)
HStack {
if showOpenActionsInHome {
AccentButton(text: "Files", imageSystemName: "folder") {
NavigationModel.shared.presentingFileImporter = true
}
AccentButton(text: "Paste", imageSystemName: "doc.on.clipboard.fill") {
OpenVideosModel.shared.openURLsFromClipboard(playbackMode: .playNow)
}
AccentButton(imageSystemName: "ellipsis") {
NavigationModel.shared.presentingOpenVideos = true
}
.frame(maxWidth: 40)
}
}
#endif
#if os(tvOS)
HStack {
if showOpenActionsInHome {
Button {
NavigationModel.shared.presentingOpenVideos = true
} label: {
Label("Open Video", systemImage: "globe")
}
}
Button {
NavigationModel.shared.presentingAccounts = true
} label: {
Label("Locations", systemImage: "globe")
}
Spacer()
HideWatchedButtons()
HideShortsButtons()
Button {
NavigationModel.shared.presentingSettings = true
} label: {
Label("Settings", systemImage: "gear")
}
}
#if os(tvOS)
.font(.caption)
.imageScale(.small)
#endif
#endif
}
.padding(.top, 15)
#if os(tvOS)
.padding(.horizontal, 40)
#else
.padding(.horizontal, 15)
#endif
if showQueueInHome {
QueueView()
#if os(tvOS)
.padding(.horizontal, 40)
#else
.padding(.horizontal, 15)
#endif
}
if !accounts.current.isNil, showFavoritesInHome {
VStack(alignment: .leading) {
#if os(tvOS)
ForEach(Defaults[.favorites]) { item in
FavoriteItemView(item: item, favoritesChanged: $favoritesChanged)
}
#else
ForEach(favorites) { item in
FavoriteItemView(item: item, favoritesChanged: $favoritesChanged)
#if os(macOS)
.workaroundForVerticalScrollingBug()
#endif
}
#endif
}
}
#if !os(tvOS)
Color.clear.padding(.bottom, 60)
#endif
}
.onAppear {
updateTask = Task {
async let favoritesUpdates: Void = {
for await _ in Defaults.updates(.favorites) {
favoritesChanged.toggle()
}
}()
async let widgetsUpdates: Void = {
for await _ in Defaults.updates(.widgetsSettings) {
favoritesChanged.toggle()
}
}()
_ = await (favoritesUpdates, widgetsUpdates)
}
}
.onDisappear {
updateTask?.cancel()
}
.onChange(of: player.presentingPlayer) { presenting in
if presenting {
updateTask?.cancel()
} else {
updateTask = Task {
async let favoritesUpdates: Void = {
for await _ in Defaults.updates(.favorites) {
favoritesChanged.toggle()
}
}()
async let widgetsUpdates: Void = {
for await _ in Defaults.updates(.widgetsSettings) {
favoritesChanged.toggle()
}
}()
_ = await (favoritesUpdates, widgetsUpdates)
}
}
}
.redrawOn(change: favoritesChanged)
#if os(tvOS)
.edgesIgnoringSafeArea(.horizontal)
#else
.navigationTitle("Home")
#endif
#if os(macOS)
.background(Color.secondaryBackground)
.frame(minWidth: Constants.contentViewMinWidth)
.toolbar {
ToolbarItemGroup(placement: .automatic) {
HideWatchedButtons()
HideShortsButtons()
HomeSettingsButton()
}
}
#endif
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
homeMenu
}
}
#endif
#if !os(macOS)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
favoritesChanged.toggle()
}
#endif
}
func sectionLabel(_ label: String) -> some View {
Text(label.localized())
#if os(tvOS)
.padding(.horizontal, 40)
#else
.padding(.horizontal, 15)
#endif
.font(.title3.bold())
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.secondary)
}
#if os(iOS)
var homeMenu: some View {
Menu {
Section {
HideWatchedButtons()
HideShortsButtons()
}
Section {
Button {
navigation.presentingHomeSettings = true
} label: {
Label("Home Settings", systemImage: "gear")
}
}
} label: {
HStack(spacing: 12) {
Text("Home")
.foregroundColor(.primary)
.font(.headline)
Image(systemName: "chevron.down.circle.fill")
.foregroundColor(.accentColor)
.imageScale(.small)
}
.transaction { t in t.animation = nil }
}
}
#endif
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
TabView {
NavigationView {
HomeView()
.injectFixtureEnvironmentObjects()
.tabItem {
Label("Home", systemImage: "house")
}
}
}
}
}