mirror of
https://github.com/yattee/yattee.git
synced 2025-11-15 22:48:43 +00:00
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.
This commit is contained in:
@@ -34,7 +34,7 @@ struct FavoriteItemView: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
if isVisible {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
itemControl
|
||||
.contextMenu { contextMenu }
|
||||
.contentShape(Rectangle())
|
||||
@@ -64,24 +64,34 @@ struct FavoriteItemView: View {
|
||||
#else
|
||||
.padding(.horizontal, 15)
|
||||
#endif
|
||||
.frame(height: expectedContentHeight)
|
||||
} else {
|
||||
Group {
|
||||
switch widgetListingStyle {
|
||||
case .horizontalCells:
|
||||
HorizontalCells(items: limitedItems)
|
||||
case .list:
|
||||
ListView(items: limitedItems)
|
||||
.padding(.vertical, 10)
|
||||
#if os(tvOS)
|
||||
.padding(.leading, 40)
|
||||
#else
|
||||
.padding(.horizontal, 15)
|
||||
#endif
|
||||
ZStack(alignment: .topLeading) {
|
||||
// Reserve space immediately to prevent layout shift
|
||||
Color.clear
|
||||
.frame(height: expectedContentHeight)
|
||||
|
||||
// Actual content renders within the reserved space
|
||||
Group {
|
||||
switch widgetListingStyle {
|
||||
case .horizontalCells:
|
||||
HorizontalCells(items: limitedItems)
|
||||
case .list:
|
||||
ListView(items: limitedItems)
|
||||
.padding(.vertical, 10)
|
||||
#if os(tvOS)
|
||||
.padding(.leading, 40)
|
||||
#else
|
||||
.padding(.horizontal, 15)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.environment(\.inChannelView, inChannelView)
|
||||
}
|
||||
.environment(\.inChannelView, inChannelView)
|
||||
.animation(nil, value: store.contentItems.count)
|
||||
}
|
||||
}
|
||||
.animation(nil, value: store.contentItems.count)
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if item.section == .history {
|
||||
@@ -233,6 +243,23 @@ struct FavoriteItemView: View {
|
||||
favoritesModel.listingStyle(item)
|
||||
}
|
||||
|
||||
var expectedContentHeight: Double {
|
||||
switch widgetListingStyle {
|
||||
case .horizontalCells:
|
||||
#if os(tvOS)
|
||||
return 600
|
||||
#else
|
||||
return 290
|
||||
#endif
|
||||
case .list:
|
||||
// Approximate height for list view items
|
||||
let itemCount = favoritesModel.limit(item)
|
||||
let itemHeight: Double = 70 // Approximate height per item
|
||||
let padding: Double = 20
|
||||
return Double(itemCount) * itemHeight + padding
|
||||
}
|
||||
}
|
||||
|
||||
func loadCacheAndResource(force: Bool = false) {
|
||||
guard let resource else { return }
|
||||
|
||||
|
||||
@@ -30,91 +30,93 @@ struct HomeView: View {
|
||||
|
||||
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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
.foregroundColor(.primary)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
.padding(.top, 15)
|
||||
#if os(tvOS)
|
||||
.padding(.horizontal, 40)
|
||||
#else
|
||||
.padding(.horizontal, 15)
|
||||
#endif
|
||||
|
||||
if showQueueInHome {
|
||||
QueueView()
|
||||
#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 !accounts.current.isNil, showFavoritesInHome {
|
||||
VStack(alignment: .leading) {
|
||||
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
|
||||
#if !os(tvOS)
|
||||
Color.clear.padding(.bottom, 60)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.animation(nil, value: favoritesChanged)
|
||||
.onAppear {
|
||||
updateTask = Task {
|
||||
async let favoritesUpdates: Void = {
|
||||
|
||||
@@ -6,7 +6,7 @@ struct QueueView: View {
|
||||
@ObservedObject private var player = PlayerModel.shared
|
||||
|
||||
var body: some View {
|
||||
LazyVStack {
|
||||
VStack {
|
||||
if !items.isEmpty {
|
||||
Button {
|
||||
withAnimation {
|
||||
|
||||
@@ -411,7 +411,7 @@ struct PlaybackSettings: View {
|
||||
}
|
||||
.transaction { t in t.animation = .none }
|
||||
.buttonStyle(.plain)
|
||||
.frame(height: 40, alignment: .trailing)
|
||||
.frame(width: 140, height: 40, alignment: .trailing)
|
||||
#else
|
||||
StreamControl(focusedField: $focusedField)
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,7 @@ struct HorizontalCells: View {
|
||||
}
|
||||
.frame(height: cellHeight)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
.animation(nil, value: contentItems.count)
|
||||
}
|
||||
|
||||
var contentItems: [ContentItem] {
|
||||
|
||||
@@ -10,9 +10,10 @@ struct ListView: View {
|
||||
ContentItemView(item: item)
|
||||
.environment(\.listingStyle, .list)
|
||||
.environment(\.noListingDividers, limit == 1)
|
||||
.transition(.opacity)
|
||||
.transition(.identity)
|
||||
}
|
||||
}
|
||||
.animation(nil, value: limitedItems.count)
|
||||
}
|
||||
|
||||
var limitedItems: [ContentItem] {
|
||||
|
||||
Reference in New Issue
Block a user