diff --git a/Swiftfin tvOS/Components/ErrorView.swift b/Swiftfin tvOS/Components/ErrorView.swift new file mode 100644 index 000000000..0a9c33c85 --- /dev/null +++ b/Swiftfin tvOS/Components/ErrorView.swift @@ -0,0 +1,47 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2025 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +// TODO: should use environment refresh instead? +struct ErrorView: View { + + private let error: ErrorType + private var onRetry: (() -> Void)? + + var body: some View { + VStack(spacing: 20) { + Image(systemName: "xmark.circle.fill") + .font(.system(size: 150)) + .foregroundColor(Color.red) + + Text(error.localizedDescription) + .frame(minWidth: 250, maxWidth: 750) + .multilineTextAlignment(.center) + + if let onRetry { + PrimaryButton(title: L10n.retry) + .onSelect(onRetry) + } + } + } +} + +extension ErrorView { + + init(error: ErrorType) { + self.init( + error: error, + onRetry: nil + ) + } + + func onRetry(_ action: @escaping () -> Void) -> Self { + copy(modifying: \.onRetry, with: action) + } +} diff --git a/Swiftfin tvOS/Components/ListRowButton.swift b/Swiftfin tvOS/Components/ListRowButton.swift index 7b964e3d8..f5818cbf9 100644 --- a/Swiftfin tvOS/Components/ListRowButton.swift +++ b/Swiftfin tvOS/Components/ListRowButton.swift @@ -10,16 +10,32 @@ import SwiftUI struct ListRowButton: View { + // MARK: - Environment + + @Environment(\.isEnabled) + private var isEnabled + + // MARK: - Focus State + + @FocusState + private var isFocused: Bool + + // MARK: - Button Variables + let title: String let role: ButtonRole? let action: () -> Void + // MARK: - Initializer + init(_ title: String, role: ButtonRole? = nil, action: @escaping () -> Void) { self.title = title self.role = role self.action = action } + // MARK: - Body + var body: some View { Button { action() @@ -28,16 +44,23 @@ struct ListRowButton: View { RoundedRectangle(cornerRadius: 10) .fill(secondaryStyle) + if !isEnabled { + Color.black.opacity(0.5) + } else if isFocused { + Color.white.opacity(0.25) + } + Text(title) .foregroundStyle(primaryStyle) .font(.body.weight(.bold)) } } .buttonStyle(.card) - .frame(height: 75) + .frame(maxHeight: 75) + .focused($isFocused) } - // MARK: - Styles + // MARK: - Primary Style private var primaryStyle: some ShapeStyle { if role == .destructive { @@ -47,6 +70,8 @@ struct ListRowButton: View { } } + // MARK: - Secondary Style + private var secondaryStyle: some ShapeStyle { if role == .destructive { return AnyShapeStyle(Color.red.opacity(0.2)) diff --git a/Swiftfin tvOS/Components/PrimaryButton.swift b/Swiftfin tvOS/Components/PrimaryButton.swift new file mode 100644 index 000000000..c7118bd80 --- /dev/null +++ b/Swiftfin tvOS/Components/PrimaryButton.swift @@ -0,0 +1,76 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2025 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +struct PrimaryButton: View { + + // MARK: - Defaults + + @Default(.accentColor) + private var accentColor + + // MARK: - Environment + + @Environment(\.isEnabled) + private var isEnabled + + // MARK: - Focus State + + @FocusState + private var isFocused: Bool + + // MARK: - Button Variables + + private let title: String + private var onSelect: () -> Void + + // MARK: - Body + + var body: some View { + Button { + onSelect() + } label: { + ZStack { + RoundedRectangle(cornerRadius: 10) + .fill(accentColor) + + if !isEnabled { + Color.black.opacity(0.5) + } else if isFocused { + Color.white.opacity(0.25) + } + + Text(title) + .fontWeight(.bold) + .foregroundStyle(isFocused ? Color.black : accentColor.overlayColor) + } + } + .buttonStyle(.card) + .frame(height: 75) + .frame(maxWidth: 750) + .focused($isFocused) + } +} + +extension PrimaryButton { + + // MARK: - Initializer + + init(title: String) { + self.init( + title: title, + onSelect: {} + ) + } + + func onSelect(_ action: @escaping () -> Void) -> Self { + copy(modifying: \.onSelect, with: action) + } +} diff --git a/Swiftfin tvOS/Views/AppLoadingView.swift b/Swiftfin tvOS/Views/AppLoadingView.swift index 1fcdc22ce..21ccb3fcf 100644 --- a/Swiftfin tvOS/Views/AppLoadingView.swift +++ b/Swiftfin tvOS/Views/AppLoadingView.swift @@ -18,10 +18,20 @@ struct AppLoadingView: View { ZStack { Color.clear + if !didFailMigration { + ProgressView() + } + if didFailMigration { - Text("An internal error occurred.") + ErrorView(error: JellyfinAPIError("An internal error occurred.")) } } + .topBarTrailing { + Button(L10n.advanced, systemImage: "gearshape.fill") {} + .foregroundStyle(.secondary) + .disabled(true) + .opacity(didFailMigration ? 0 : 1) + } .onNotification(.didFailMigration) { _ in didFailMigration = true } diff --git a/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift b/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift index 8a2ad4c48..3fe78240c 100644 --- a/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift +++ b/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift @@ -40,7 +40,7 @@ struct ChannelLibraryView: View { } var body: some View { - WrappedView { + ZStack { switch viewModel.state { case .content: if viewModel.elements.isEmpty { @@ -49,11 +49,15 @@ struct ChannelLibraryView: View { contentView } case let .error(error): - Text(error.localizedDescription) + ErrorView(error: error) + .onRetry { + viewModel.send(.refresh) + } case .initial, .refreshing: ProgressView() } } + .animation(.linear(duration: 0.1), value: viewModel.state) .ignoresSafeArea() .onFirstAppear { if viewModel.state == .initial { diff --git a/Swiftfin tvOS/Views/HomeView/HomeErrorView.swift b/Swiftfin tvOS/Views/HomeView/HomeErrorView.swift deleted file mode 100644 index b7d62961a..000000000 --- a/Swiftfin tvOS/Views/HomeView/HomeErrorView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2025 Jellyfin & Jellyfin Contributors -// - -import SwiftUI - -// TODO: make general `ErrorView` like iOS - -#warning("TODO: implement") - -extension HomeView { - - struct ErrorView: View { - - @ObservedObject - var viewModel: HomeViewModel - - var body: some View { - Text("TODO") - } - } -} - -// extension HomeView { -// -// struct ErrorView: View { -// -// @ObservedObject -// var viewModel: HomeViewModel -// -// let errorMessage: ErrorMessage -// -// var body: some View { -// VStack { -// if viewModel.isLoading { -// ProgressView() -// .frame(width: 100, height: 100) -// .scaleEffect(2) -// } else { -// Image(systemName: "xmark.circle.fill") -// .font(.system(size: 72)) -// .foregroundColor(Color.red) -// .frame(width: 100, height: 100) -// } -// -//// Text("\(errorMessage.code)") -// -// Text(errorMessage.message) -// .frame(minWidth: 50, maxWidth: 240) -// .multilineTextAlignment(.center) -// -// Button { -//// viewModel.refresh() -// } label: { -// L10n.retry.text -// .bold() -// .font(.callout) -// .frame(width: 400, height: 75) -// .background(Color.jellyfinPurple) -// } -// .buttonStyle(.card) -// } -// .offset(y: -50) -// } -// } -// } diff --git a/Swiftfin tvOS/Views/HomeView/HomeView.swift b/Swiftfin tvOS/Views/HomeView/HomeView.swift index 5cfb0b65b..73abe5cb9 100644 --- a/Swiftfin tvOS/Views/HomeView/HomeView.swift +++ b/Swiftfin tvOS/Views/HomeView/HomeView.swift @@ -50,19 +50,23 @@ struct HomeView: View { } var body: some View { - WrappedView { - Group { - switch viewModel.state { - case .content: - contentView - case let .error(error): - Text(error.localizedDescription) - case .initial, .refreshing: - ProgressView() - } + ZStack { + // This keeps the ErrorView vertically aligned with the PagingLibraryView + Color.clear + + switch viewModel.state { + case .content: + contentView + case let .error(error): + ErrorView(error: error) + .onRetry { + viewModel.send(.refresh) + } + case .initial, .refreshing: + ProgressView() } - .transition(.opacity.animation(.linear(duration: 0.2))) } + .animation(.linear(duration: 0.1), value: viewModel.state) .onFirstAppear { viewModel.send(.refresh) } diff --git a/Swiftfin tvOS/Views/ItemView/ItemView.swift b/Swiftfin tvOS/Views/ItemView/ItemView.swift index cce801081..ae32609ab 100644 --- a/Swiftfin tvOS/Views/ItemView/ItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/ItemView.swift @@ -52,17 +52,20 @@ struct ItemView: View { } var body: some View { - WrappedView { + ZStack { switch viewModel.state { case .content: contentView case let .error(error): - Text(error.localizedDescription) + ErrorView(error: error) + .onRetry { + viewModel.send(.refresh) + } case .initial, .refreshing: ProgressView() } } - .transition(.opacity.animation(.linear(duration: 0.2))) + .animation(.linear(duration: 0.1), value: viewModel.state) .onFirstAppear { viewModel.send(.refresh) } diff --git a/Swiftfin tvOS/Views/MediaView/MediaView.swift b/Swiftfin tvOS/Views/MediaView/MediaView.swift index 961f95b3a..2d3cbc2a6 100644 --- a/Swiftfin tvOS/Views/MediaView/MediaView.swift +++ b/Swiftfin tvOS/Views/MediaView/MediaView.swift @@ -52,19 +52,23 @@ struct MediaView: View { } var body: some View { - WrappedView { - Group { - switch viewModel.state { - case .content: - contentView - case let .error(error): - Text(error.localizedDescription) - case .initial, .refreshing: - ProgressView() - } + ZStack { + // This keeps the ErrorView vertically aligned with the PagingLibraryView + Color.clear + + switch viewModel.state { + case .content: + contentView + case let .error(error): + ErrorView(error: error) + .onRetry { + viewModel.send(.refresh) + } + case .initial, .refreshing: + ProgressView() } - .transition(.opacity.animation(.linear(duration: 0.2))) } + .animation(.linear(duration: 0.1), value: viewModel.state) .ignoresSafeArea() .onFirstAppear { viewModel.send(.refresh) diff --git a/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift b/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift index 52f7355e9..c7754a7fc 100644 --- a/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift +++ b/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift @@ -247,11 +247,10 @@ struct PagingLibraryView: View { @ViewBuilder private func errorView(with error: some Error) -> some View { - Text(error.localizedDescription) - /* ErrorView(error: error) - .onRetry { - viewModel.send(.refresh) - } */ + ErrorView(error: error) + .onRetry { + viewModel.send(.refresh) + } } // MARK: Grid View diff --git a/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift b/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift index 686854cf0..7eabf0b66 100644 --- a/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift +++ b/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift @@ -78,7 +78,7 @@ struct ProgramsView: View { } var body: some View { - WrappedView { + ZStack { switch programsViewModel.state { case .content: if programsViewModel.hasNoResults { @@ -87,11 +87,15 @@ struct ProgramsView: View { contentView } case let .error(error): - Text(error.localizedDescription) + ErrorView(error: error) + .onRetry { + programsViewModel.send(.refresh) + } case .initial, .refreshing: ProgressView() } } + .animation(.linear(duration: 0.1), value: programsViewModel.state) .ignoresSafeArea(edges: [.bottom, .horizontal]) .onFirstAppear { if programsViewModel.state == .initial { diff --git a/Swiftfin tvOS/Views/QuickConnectView.swift b/Swiftfin tvOS/Views/QuickConnectView.swift index ad5e68939..28848e2f0 100644 --- a/Swiftfin tvOS/Views/QuickConnectView.swift +++ b/Swiftfin tvOS/Views/QuickConnectView.swift @@ -47,7 +47,7 @@ struct QuickConnectView: View { } var body: some View { - WrappedView { + ZStack { switch viewModel.state { case .idle, .authenticated: Color.clear @@ -56,10 +56,13 @@ struct QuickConnectView: View { case let .polling(code): pollingView(code: code) case let .error(error): - Text(error.localizedDescription) -// ErrorView(error: error) + ErrorView(error: error) + .onRetry { + viewModel.start() + } } } + .animation(.linear(duration: 0.1), value: viewModel.state) .edgePadding() .navigationTitle(L10n.quickConnect) .onFirstAppear { diff --git a/Swiftfin tvOS/Views/SearchView.swift b/Swiftfin tvOS/Views/SearchView.swift index ce6b6ec97..2ba04fa94 100644 --- a/Swiftfin tvOS/Views/SearchView.swift +++ b/Swiftfin tvOS/Views/SearchView.swift @@ -110,25 +110,26 @@ struct SearchView: View { } var body: some View { - WrappedView { - Group { - switch viewModel.state { - case let .error(error): - Text(error.localizedDescription) - case .initial: - suggestionsView - case .content: - if viewModel.hasNoResults { - L10n.noResults.text - } else { - resultsView - } - case .searching: - ProgressView() + ZStack { + switch viewModel.state { + case .initial: + suggestionsView + case .content: + if viewModel.hasNoResults { + L10n.noResults.text + } else { + resultsView } + case let .error(error): + ErrorView(error: error) + .onRetry { + viewModel.send(.search(query: searchQuery)) + } + case .searching: + ProgressView() } - .transition(.opacity.animation(.linear(duration: 0.2))) } + .animation(.linear(duration: 0.1), value: viewModel.state) .ignoresSafeArea(edges: [.bottom, .horizontal]) .onFirstAppear { viewModel.send(.getSuggestions) diff --git a/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift b/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift index 2f61a93da..b7d39b984 100644 --- a/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift +++ b/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift @@ -78,7 +78,7 @@ extension SelectUserView { ListRowButton(L10n.delete, role: .destructive) { onDelete() } - .frame(width: 400, height: 50) + .frame(width: 400, height: 75) .disabled(!areUsersSelected) } diff --git a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift index 69be51e15..89e553c35 100644 --- a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift @@ -44,22 +44,14 @@ struct SettingsView: View { .onSelect { router.route(to: \.serverDetail, viewModel.userSession.server) } + } - Button { + Section { + ListRowButton(L10n.switchUser) { viewModel.signOut() - } label: { - HStack { - - Text(L10n.switchUser) - .foregroundColor(.jellyfinPurple) - - Spacer() - - Image(systemName: "chevron.right") - .font(.body.weight(.regular)) - .foregroundColor(.secondary) - } } + .foregroundStyle(Color.jellyfinPurple.overlayColor, Color.jellyfinPurple) + .listRowInsets(.zero) } Section(L10n.videoPlayer) { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 5bbabb062..e3924241d 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -244,6 +244,8 @@ 4EED874B2CBF824B002354D2 /* DevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED87482CBF824B002354D2 /* DevicesView.swift */; }; 4EED87512CBF84AD002354D2 /* DevicesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED874F2CBF84AD002354D2 /* DevicesViewModel.swift */; }; 4EEEEA242CFA8E1500527D79 /* NavigationBarMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEEEA232CFA8E1500527D79 /* NavigationBarMenuButton.swift */; }; + 4EF0DCA72D4965D9005A5194 /* PrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF0DCA62D4965D9005A5194 /* PrimaryButton.swift */; }; + 4EF0DCA92D49751B005A5194 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF0DCA82D49751B005A5194 /* ErrorView.swift */; }; 4EF18B262CB9934C00343666 /* LibraryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF18B252CB9934700343666 /* LibraryRow.swift */; }; 4EF18B282CB9936D00343666 /* ListColumnsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF18B272CB9936400343666 /* ListColumnsPickerView.swift */; }; 4EF18B2A2CB993BD00343666 /* ListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF18B292CB993AD00343666 /* ListRow.swift */; }; @@ -957,7 +959,6 @@ E1A3E4CF2BB7E02B005C59F8 /* DelayedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4CE2BB7E02B005C59F8 /* DelayedProgressView.swift */; }; E1A3E4D12BB7F5BF005C59F8 /* ErrorCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4D02BB7F5BF005C59F8 /* ErrorCard.swift */; }; E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */; }; - E1A42E4F28CBD3E100A14DCB /* HomeErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */; }; E1A42E5128CBE44500A14DCB /* LandscapePosterProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */; }; E1A5056A2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; }; E1A5056B2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; }; @@ -1398,6 +1399,8 @@ 4EED87482CBF824B002354D2 /* DevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesView.swift; sourceTree = ""; }; 4EED874F2CBF84AD002354D2 /* DevicesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesViewModel.swift; sourceTree = ""; }; 4EEEEA232CFA8E1500527D79 /* NavigationBarMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarMenuButton.swift; sourceTree = ""; }; + 4EF0DCA62D4965D9005A5194 /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; + 4EF0DCA82D49751B005A5194 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 4EF18B252CB9934700343666 /* LibraryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryRow.swift; sourceTree = ""; }; 4EF18B272CB9936400343666 /* ListColumnsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListColumnsPickerView.swift; sourceTree = ""; }; 4EF18B292CB993AD00343666 /* ListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRow.swift; sourceTree = ""; }; @@ -1866,7 +1869,6 @@ E1A3E4CE2BB7E02B005C59F8 /* DelayedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelayedProgressView.swift; sourceTree = ""; }; E1A3E4D02BB7F5BF005C59F8 /* ErrorCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorCard.swift; sourceTree = ""; }; E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicItemSelector.swift; sourceTree = ""; }; - E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeErrorView.swift; sourceTree = ""; }; E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapePosterProgressBar.swift; sourceTree = ""; }; E1A505692D0B733F007EE305 /* Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = ""; }; E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; @@ -3245,6 +3247,7 @@ E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */, E1C92618288756BD002A7A66 /* DotHStack.swift */, E12E30F4296392EC0022FAC9 /* EnumPickerView.swift */, + 4EF0DCA82D49751B005A5194 /* ErrorView.swift */, E1549677296CB22B00C4EF88 /* InlineEnumToggle.swift */, E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */, E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */, @@ -3252,6 +3255,7 @@ 4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */, E1C92617288756BD002A7A66 /* PosterButton.swift */, E1C92619288756BD002A7A66 /* PosterHStack.swift */, + 4EF0DCA62D4965D9005A5194 /* PrimaryButton.swift */, E12CC1C428D12D9B00678D5D /* SeeAllPosterButton.swift */, E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */, E17885A3278105170094FBCF /* SFSymbolButton.swift */, @@ -4620,7 +4624,6 @@ isa = PBXGroup; children = ( E12CC1C328D12D6300678D5D /* Components */, - E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */, 531690E6267ABD79005D8AB9 /* HomeView.swift */, ); path = HomeView; @@ -5496,7 +5499,6 @@ 4E2AC4C32C6C491200DD600D /* AudoCodec.swift in Sources */, E1575EA6293E7D40001665B1 /* VideoPlayer.swift in Sources */, E185920628CDAA6400326F80 /* CastAndCrewHStack.swift in Sources */, - E1A42E4F28CBD3E100A14DCB /* HomeErrorView.swift in Sources */, 4E8F74AB2CE03DD300CC8969 /* DeleteItemViewModel.swift in Sources */, 53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */, E122A9142788EAAD0060FA63 /* MediaStream.swift in Sources */, @@ -5672,6 +5674,7 @@ E1575E84293E7A00001665B1 /* PrimaryAppIcon.swift in Sources */, E1153DCD2BBB633B00424D36 /* FastSVGView.swift in Sources */, E1ED7FE22CAA6BAF00ACB6E3 /* ServerLogsViewModel.swift in Sources */, + 4EF0DCA92D49751B005A5194 /* ErrorView.swift in Sources */, E1CB75762C80EAFA00217C76 /* ArrayBuilder.swift in Sources */, E102315B2BCF8AF8009D71FC /* ProgramProgressOverlay.swift in Sources */, E1E6C45129B104850064123F /* Button.swift in Sources */, @@ -5681,6 +5684,7 @@ E1A1528B28FD22F600600579 /* TextPairView.swift in Sources */, E11042762B8013DF00821020 /* Stateful.swift in Sources */, 091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */, + 4EF0DCA72D4965D9005A5194 /* PrimaryButton.swift in Sources */, E1575E66293E77B5001665B1 /* Poster.swift in Sources */, E18E021F2887492B0022598C /* SystemImageContentView.swift in Sources */, E19D41B42BF2C0020082B8B2 /* StoredValues+Temp.swift in Sources */,