mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 10:25:02 +00:00
Fix tvOS focus trap on empty Home after fresh install
Render a focusable empty state on tvOS Home when no sections have content, with an "Open Sources" button that switches the sidebar selection. Without a focusable view the tvOS focus engine had no target, leaving the sidebar unreachable after the initial iCloud alert was dismissed. Also wire the selectedSidebarItem onChange handler into the tvOS TabView, which was missing and prevented programmatic sidebar selection.
This commit is contained in:
@@ -3624,6 +3624,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"home.tvos.empty.title" : {
|
||||||
|
"comment" : "tvOS Home empty-state title shown on fresh install",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Welcome to Yattee"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home.tvos.empty.message" : {
|
||||||
|
"comment" : "tvOS Home empty-state message shown on fresh install",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Add a media source to start watching."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home.tvos.empty.openSources" : {
|
||||||
|
"comment" : "tvOS Home empty-state button that opens the Sources screen",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Open Sources"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"home.history.clear" : {
|
"home.history.clear" : {
|
||||||
"comment" : "Clear history button",
|
"comment" : "Clear history button",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -166,12 +166,63 @@ struct HomeView: View {
|
|||||||
|
|
||||||
// MARK: - Main Content
|
// MARK: - Main Content
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
/// True on tvOS when no Home section would render content — prevents a focus trap
|
||||||
|
/// on fresh installs where the detail pane would otherwise be completely empty.
|
||||||
|
private var isHomeEmpty: Bool {
|
||||||
|
let sections = settingsManager?.visibleSections() ?? HomeSectionItem.defaultOrder.filter { HomeSectionItem.defaultVisibility[$0] == true }
|
||||||
|
for section in sections {
|
||||||
|
switch section {
|
||||||
|
case .continueWatching:
|
||||||
|
if !recentContinueWatching.isEmpty { return false }
|
||||||
|
case .feed:
|
||||||
|
if !feedCache.videos.isEmpty { return false }
|
||||||
|
case .bookmarks:
|
||||||
|
if !recentBookmarks.isEmpty { return false }
|
||||||
|
case .history:
|
||||||
|
if !recentHistory.isEmpty { return false }
|
||||||
|
case .downloads:
|
||||||
|
break
|
||||||
|
case .instanceContent, .mediaSource:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private var emptyHomeView: some View {
|
||||||
|
VStack(spacing: 32) {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Text(String(localized: "home.tvos.empty.title"))
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
Text(String(localized: "home.tvos.empty.message"))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
appEnvironment?.navigationCoordinator.selectedSidebarItem = .sources
|
||||||
|
} label: {
|
||||||
|
Label(String(localized: "home.tvos.empty.openSources"), systemImage: "externaldrive.connected.to.line.below")
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(60)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
ScrollView {
|
if isHomeEmpty {
|
||||||
LazyVStack(spacing: 0) {
|
emptyHomeView
|
||||||
homeContent
|
} else {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(spacing: 0) {
|
||||||
|
homeContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -664,6 +664,11 @@ struct UnifiedTabView: View {
|
|||||||
.onChange(of: navigationCoordinator?.pendingNavigation) { _, newValue in
|
.onChange(of: navigationCoordinator?.pendingNavigation) { _, newValue in
|
||||||
handlePendingNavigation(newValue)
|
handlePendingNavigation(newValue)
|
||||||
}
|
}
|
||||||
|
.onChange(of: navigationCoordinator?.selectedSidebarItem) { _, newItem in
|
||||||
|
guard let item = newItem else { return }
|
||||||
|
selection = item
|
||||||
|
navigationCoordinator?.selectedSidebarItem = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the configured startup tab on first appearance.
|
/// Applies the configured startup tab on first appearance.
|
||||||
|
|||||||
Reference in New Issue
Block a user