mirror of
https://github.com/yattee/yattee.git
synced 2026-04-10 01:26:57 +00:00
Fix ContentUnavailableView centering on Apple TV
On tvOS, ContentUnavailableView inside a Group doesn't expand to fill available space — it sizes to content and aligns top-leading. Add .frame(maxWidth: .infinity, maxHeight: .infinity) to all instances so they center correctly in their parent containers.
This commit is contained in:
@@ -841,6 +841,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noDescription"), systemImage: "text.alignleft")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
}
|
||||
@@ -869,6 +870,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noVideos"), systemImage: "play.rectangle")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else if !filteredVideos.isEmpty {
|
||||
VideoListContent(listStyle: listStyle) {
|
||||
@@ -965,6 +967,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noVideos"), systemImage: "play.rectangle")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -1006,6 +1009,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noPlaylists"), systemImage: "list.bullet.rectangle")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else if !playlists.isEmpty {
|
||||
VideoListContent(listStyle: listStyle) {
|
||||
@@ -1068,6 +1072,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noPlaylists"), systemImage: "list.bullet.rectangle")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -1110,6 +1115,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noShorts"), systemImage: "bolt")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else if !filteredShorts.isEmpty {
|
||||
VideoListContent(listStyle: listStyle) {
|
||||
@@ -1186,6 +1192,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noShorts"), systemImage: "bolt")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -1228,6 +1235,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noStreams"), systemImage: "video")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else if !filteredStreams.isEmpty {
|
||||
VideoListContent(listStyle: listStyle) {
|
||||
@@ -1304,6 +1312,7 @@ struct ChannelView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "channel.noStreams"), systemImage: "video")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -1329,6 +1338,7 @@ struct ChannelView: View {
|
||||
.padding(.vertical, 40)
|
||||
} else if searchResults.items.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else {
|
||||
switch layout {
|
||||
@@ -1519,6 +1529,7 @@ struct ChannelView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
@@ -180,12 +180,14 @@ struct BookmarksListView: View {
|
||||
private var emptyView: some View {
|
||||
if !searchText.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "home.bookmarks.title"), systemImage: "bookmark")
|
||||
} description: {
|
||||
Text(String(localized: "home.bookmarks.empty"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ struct ContinueWatchingView: View {
|
||||
} description: {
|
||||
Text(String(localized: "home.continueWatching.empty"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
private var listContent: some View {
|
||||
|
||||
@@ -206,12 +206,14 @@ struct HistoryListView: View {
|
||||
private var emptyView: some View {
|
||||
if !searchText.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "home.history.title"), systemImage: "clock")
|
||||
} description: {
|
||||
Text(String(localized: "home.history.empty"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ struct PlaylistsListView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - List Content
|
||||
|
||||
@@ -590,6 +590,7 @@ struct InstanceBrowseView: View {
|
||||
} description: {
|
||||
Text(String(localized: "playlists.empty.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Empty View
|
||||
@@ -600,6 +601,7 @@ struct InstanceBrowseView: View {
|
||||
} description: {
|
||||
Text(String(localized: "instance.browse.noVideos"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Error View
|
||||
@@ -615,6 +617,7 @@ struct InstanceBrowseView: View {
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Search Results
|
||||
@@ -778,6 +781,7 @@ struct InstanceBrowseView: View {
|
||||
} description: {
|
||||
Text(String(localized: "search.hint.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -815,6 +819,7 @@ struct InstanceBrowseView: View {
|
||||
} description: {
|
||||
Text(String(localized: "search.noResults.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
private func searchErrorView(_ error: String) -> some View {
|
||||
@@ -828,6 +833,7 @@ struct InstanceBrowseView: View {
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Data Loading
|
||||
|
||||
@@ -71,12 +71,14 @@ struct MediaBrowserView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else if files.isEmpty {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "mediaBrowser.emptyFolder"), systemImage: "folder")
|
||||
} description: {
|
||||
Text(String(localized: "mediaBrowser.emptyFolder.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
fileList
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ struct MediaSourcesView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
sourcesList
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ struct UnifiedPlaylistDetailView: View {
|
||||
systemImage: "list.bullet.rectangle",
|
||||
description: Text(String(localized: "playlist.notFound.description"))
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
.navigationTitle(title.isEmpty ? String(localized: "playlist.title") : title)
|
||||
@@ -507,6 +508,7 @@ struct UnifiedPlaylistDetailView: View {
|
||||
} description: {
|
||||
Text(String(localized: "playlist.empty.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.top, 40)
|
||||
}
|
||||
|
||||
@@ -547,6 +549,7 @@ struct UnifiedPlaylistDetailView: View {
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Data Loading
|
||||
|
||||
@@ -657,6 +657,7 @@ struct SearchView: View {
|
||||
} description: {
|
||||
Text(String(localized: "search.noResults.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.accessibilityIdentifier("search.noResults")
|
||||
}
|
||||
|
||||
@@ -670,6 +671,7 @@ struct SearchView: View {
|
||||
Task { await searchViewModel?.search(query: searchTextBinding.wrappedValue) }
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
/// Queue source for search results
|
||||
|
||||
@@ -47,6 +47,7 @@ struct ContributorsView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
contributorsList
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ struct ImportPlaylistsView: View {
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.accessibilityIdentifier(AccessibilityID.errorMessage)
|
||||
}
|
||||
|
||||
@@ -147,6 +148,7 @@ struct ImportPlaylistsView: View {
|
||||
systemImage: "list.bullet.rectangle",
|
||||
description: Text(String(localized: "import.playlists.emptyDescription"))
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.accessibilityIdentifier(AccessibilityID.emptyState)
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ struct ImportSubscriptionsView: View {
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.accessibilityIdentifier(AccessibilityID.errorMessage)
|
||||
}
|
||||
|
||||
@@ -118,6 +119,7 @@ struct ImportSubscriptionsView: View {
|
||||
systemImage: "person.2.slash",
|
||||
description: Text(String(localized: "import.subscriptions.emptyDescription"))
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.accessibilityIdentifier(AccessibilityID.emptyState)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ struct LegacyDataImportView: View {
|
||||
systemImage: "doc.questionmark",
|
||||
description: Text(String(localized: "migration.noDataFoundDescription"))
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
importContent
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ struct LogViewerView: View {
|
||||
Text(String(localized: "settings.advanced.logs.disabled"))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
List(loggingService.filteredEntries) { entry in
|
||||
LogEntryRow(entry: entry)
|
||||
|
||||
@@ -81,6 +81,7 @@ struct NetworkShareDiscoverySheet: View {
|
||||
service.startDiscovery()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
} else {
|
||||
// Group shares by type
|
||||
@@ -124,6 +125,7 @@ struct NetworkShareDiscoverySheet: View {
|
||||
} description: {
|
||||
Text(String(localized: "discovery.unavailable.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ struct ManageChannelNotificationsView: View {
|
||||
} description: {
|
||||
Text(errorMessage)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else if subscriptions.isEmpty {
|
||||
ContentUnavailableView {
|
||||
Label(
|
||||
@@ -191,6 +192,7 @@ struct ManageChannelNotificationsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "settings.notifications.noSubscriptions.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
ForEach(subscriptions, id: \.channelID) { subscription in
|
||||
ChannelNotificationToggle(subscription: subscription)
|
||||
|
||||
@@ -117,12 +117,14 @@ struct PeerTubeInstancesExploreView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else if displayedInstances.isEmpty {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "peertube.explore.noResults"), systemImage: "magnifyingglass")
|
||||
} description: {
|
||||
Text(String(localized: "peertube.explore.noResults.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
instancesList
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ struct ButtonConfigurationView: View {
|
||||
String(localized: "settings.playerControls.buttonNotFound"),
|
||||
systemImage: "exclamationmark.triangle"
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +283,7 @@ struct MiniPlayerButtonConfigurationView: View {
|
||||
String(localized: "settings.playerControls.buttonNotFound"),
|
||||
systemImage: "exclamationmark.triangle"
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ struct PillButtonConfigurationView: View {
|
||||
String(localized: "settings.playerControls.buttonNotFound"),
|
||||
systemImage: "exclamationmark.triangle"
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ struct SourcesListView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.accessibilityIdentifier("sources.view")
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ struct TranslationContributorsView: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "settings.translators.empty"), systemImage: "globe")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
contributorsList
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ struct ManageChannelsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "subscriptions.channels.empty"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
channelsView
|
||||
}
|
||||
@@ -232,6 +233,7 @@ struct ManageChannelsView: View {
|
||||
Group {
|
||||
if filteredChannels.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
switch layout {
|
||||
case .list:
|
||||
@@ -247,6 +249,7 @@ struct ManageChannelsView: View {
|
||||
Group {
|
||||
if filteredChannels.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
switch layout {
|
||||
case .list:
|
||||
|
||||
@@ -423,7 +423,7 @@ struct SubscriptionsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "subscriptions.noVideosFromChannel.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else {
|
||||
ForEach(Array(filteredVideos.enumerated()), id: \.element.id) { index, video in
|
||||
@@ -596,7 +596,7 @@ struct SubscriptionsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "subscriptions.noVideosFromChannel.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
} else {
|
||||
VideoGridContent(columns: gridConfig.effectiveColumns) {
|
||||
@@ -691,7 +691,7 @@ struct SubscriptionsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "subscriptions.empty.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -702,7 +702,7 @@ struct SubscriptionsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "subscriptions.yatteeServerRequired.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@ struct SubscriptionsView: View {
|
||||
} description: {
|
||||
Text(String(localized: "subscriptions.notAuthenticated.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
@@ -727,7 +727,7 @@ struct SubscriptionsView: View {
|
||||
Task { await loadFeed(forceRefresh: true) }
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
|
||||
@@ -415,6 +415,7 @@ struct DownloadQualitySheet: View {
|
||||
} description: {
|
||||
Text(String(localized: "download.noStreams.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(videoStreams.enumerated()), id: \.element.url) { index, stream in
|
||||
@@ -448,6 +449,7 @@ struct DownloadQualitySheet: View {
|
||||
ContentUnavailableView {
|
||||
Label(String(localized: "download.noAudio.title"), systemImage: "speaker.slash")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(audioStreams.enumerated()), id: \.element.url) { index, stream in
|
||||
|
||||
@@ -74,6 +74,7 @@ struct PlaylistSelectorSheet: View {
|
||||
} description: {
|
||||
Text(String(localized: "playlist.empty.description"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,6 +532,7 @@ struct VideoInfoView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Header Section
|
||||
|
||||
Reference in New Issue
Block a user