Files
yattee/Shared/Home/HomeView.swift
Arkadiusz Fal a0a54bced9 Improve layout stability and disable unwanted animations
Added height reservation to FavoriteItemView to prevent layout shifts during content loading. Changed HomeView to use LazyVStack for better performance. Converted QueueView from LazyVStack to VStack. Disabled animations on content count changes across multiple views to prevent jarring layout transitions. Added width constraint to stream button in PlaybackSettings.
2025-11-14 20:02:07 +01:00

247 lines
8.6 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) {
LazyVStack(spacing: 0, pinnedViews: []) {
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")
}
}
.font(.caption)
.imageScale(.small)
.foregroundColor(.primary)
#endif
}
.padding(.top, 15)
.padding(.bottom, 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 {
#if os(tvOS)
ForEach(Defaults[.favorites]) { item in
FavoriteItemView(item: item, favoritesChanged: $favoritesChanged)
.animation(nil, value: favoritesChanged)
}
#else
ForEach(favorites) { item in
FavoriteItemView(item: item, favoritesChanged: $favoritesChanged)
.animation(nil, value: favoritesChanged)
#if os(macOS)
.workaroundForVerticalScrollingBug()
#endif
}
#endif
}
#if !os(tvOS)
Color.clear.padding(.bottom, 60)
#endif
}
}
.animation(nil, value: favoritesChanged)
.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")
}
}
}
}
}