Skip to content

Commit

Permalink
[tvOS] ErrorViews - Creation (#1414)
Browse files Browse the repository at this point in the history
* Button cleanup & errorViews

* Change the Sign Out button to be `ListRowButton`. Sets a better height value using `maxHeight` to ensure that it doesn't exceed the `ListRow` sizing.

* deleteUsersButton needs to be manually set back to 75

* wip

---------

Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
  • Loading branch information
JPKribs and LePips authored Feb 15, 2025
1 parent 6ee2b71 commit 846aabc
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 153 deletions.
52 changes: 52 additions & 0 deletions Swiftfin tvOS/Components/ErrorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// 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

// TODO: should use environment refresh instead?
struct ErrorView<ErrorType: Error>: View {

@Default(.accentColor)
private var accentColor

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 {
ListRowButton(L10n.retry, action: onRetry)
.foregroundStyle(accentColor.overlayColor, accentColor)
.frame(maxWidth: 750)
}
}
}
}

extension ErrorView {

init(error: ErrorType) {
self.init(
error: error,
onRetry: nil
)
}

func onRetry(_ action: @escaping () -> Void) -> Self {
copy(modifying: \.onRetry, with: action)
}
}
38 changes: 29 additions & 9 deletions Swiftfin tvOS/Components/ListRowButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,70 @@

import SwiftUI

// TODO: on focus, make the cancel and destructive style
// match style like in an `alert`
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()
} label: {
Button(action: action) {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(secondaryStyle)
.brightness(isFocused ? 0.25 : 0)

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 {
if role == .destructive || role == .cancel {
return AnyShapeStyle(Color.red)
} else {
return AnyShapeStyle(.primary)
return AnyShapeStyle(HierarchicalShapeStyle.primary)
}
}

// MARK: - Secondary Style

private var secondaryStyle: some ShapeStyle {
if role == .destructive {
if role == .destructive || role == .cancel {
return AnyShapeStyle(Color.red.opacity(0.2))
} else {
return AnyShapeStyle(.secondary)
return AnyShapeStyle(HierarchicalShapeStyle.secondary)
}
}
}
12 changes: 11 additions & 1 deletion Swiftfin tvOS/Views/AppLoadingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct ChannelLibraryView: View {
}

var body: some View {
WrappedView {
ZStack {
switch viewModel.state {
case .content:
if viewModel.elements.isEmpty {
Expand All @@ -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 {
Expand Down
70 changes: 0 additions & 70 deletions Swiftfin tvOS/Views/HomeView/HomeErrorView.swift

This file was deleted.

26 changes: 15 additions & 11 deletions Swiftfin tvOS/Views/HomeView/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
9 changes: 6 additions & 3 deletions Swiftfin tvOS/Views/ItemView/ItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
26 changes: 15 additions & 11 deletions Swiftfin tvOS/Views/MediaView/MediaView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 4 additions & 5 deletions Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,10 @@ struct PagingLibraryView<Element: Poster & Identifiable>: 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
Expand Down
8 changes: 6 additions & 2 deletions Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct ProgramsView: View {
}

var body: some View {
WrappedView {
ZStack {
switch programsViewModel.state {
case .content:
if programsViewModel.hasNoResults {
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 846aabc

Please sign in to comment.