diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift index ade0eb88..122cc829 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift @@ -85,5 +85,6 @@ final class MainTabCoordinator: TabCoordinatable { @ViewBuilder func makeSettingsTab(isActive: Bool) -> some View { Image(systemName: "gearshape.fill") + .accessibilityLabel(L10n.settings) } } diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift index d44f38ec..61d8d2f8 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift @@ -17,7 +17,7 @@ extension BaseItemDto: PortraitImageStackable { id ?? "no id" } - public func imageURLContsructor(maxWidth: Int) -> URL { + public func imageURLConstructor(maxWidth: Int) -> URL { switch self.itemType { case .episode: return getSeriesPrimaryImage(maxWidth: maxWidth) diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift index 5b72e8cb..b26fef16 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift @@ -68,7 +68,7 @@ extension BaseItemPerson: PortraitImageStackable { (id ?? "noid") + title + (subtitle ?? "nodescription") + blurHash + failureInitials } - public func imageURLContsructor(maxWidth: Int) -> URL { + public func imageURLConstructor(maxWidth: Int) -> URL { self.getImage(baseURL: SessionManager.main.currentLogin.server.currentURI, maxWidth: maxWidth) } diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index efa04c1b..3d7d1eb7 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -356,6 +356,8 @@ internal enum L10n { internal static var system: String { return L10n.tr("Localizable", "system") } /// Tags internal static var tags: String { return L10n.tr("Localizable", "tags") } + /// Too Many Redirects + internal static var tooManyRedirects: String { return L10n.tr("Localizable", "tooManyRedirects") } /// Try again internal static var tryAgain: String { return L10n.tr("Localizable", "tryAgain") } /// TV Shows diff --git a/Shared/Objects/PortraitImageStackable.swift b/Shared/Objects/PortraitImageStackable.swift index 07af7bda..9d4c7d30 100644 --- a/Shared/Objects/PortraitImageStackable.swift +++ b/Shared/Objects/PortraitImageStackable.swift @@ -9,7 +9,7 @@ import Foundation public protocol PortraitImageStackable { - func imageURLContsructor(maxWidth: Int) -> URL + func imageURLConstructor(maxWidth: Int) -> URL var title: String { get } var subtitle: String? { get } var blurHash: String { get } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index aa86ed37..426b5a37 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -44,7 +44,8 @@ final class ConnectToServerViewModel: ViewModel { return message } - func connectToServer(uri: String) { + func connectToServer(uri: String, redirectCount: Int = 0) { + #if targetEnvironment(simulator) var uri = uri if uri == "localhost" { @@ -63,6 +64,27 @@ final class ConnectToServerViewModel: ViewModel { case .finished: () case let .failure(error): switch error { + case is ErrorResponse: + let errorResponse = error as! ErrorResponse + switch errorResponse { + case let .error(_, _, response, _): + // a url in the response is the result if a redirect + if let newURL = response?.url { + if redirectCount > 2 { + self.handleAPIRequestError(displayMessage: L10n.tooManyRedirects, + logLevel: .critical, + tag: "connectToServer", + completion: completion) + } else { + self + .connectToServer(uri: newURL.absoluteString + .removeRegexMatches(pattern: "/web/index.html"), + redirectCount: redirectCount + 1) + } + } else { + self.handleAPIRequestError(completion: completion) + } + } case is SwiftfinStore.Errors: let swiftfinError = error as! SwiftfinStore.Errors switch swiftfinError { diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 911e932b..49a32be0 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -37,6 +37,7 @@ struct ImageView: View { Text(failureInitials) .font(.largeTitle) .foregroundColor(.secondary) + .accessibilityHidden(true) } } diff --git a/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift b/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift index 412eee34..ef5de88f 100644 --- a/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift +++ b/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift @@ -34,6 +34,7 @@ struct EpisodeRowCard: View { } } .padding(.top) + .accessibilityIgnoresInvertColors() VStack(alignment: .leading) { Text(episode.getEpisodeLocator() ?? "S-:E-") diff --git a/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift b/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift index 10c5f2ff..3bcac987 100644 --- a/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift +++ b/Swiftfin/Components/EpisodesRowView/EpisodesRowView.swift @@ -25,6 +25,7 @@ struct EpisodesRowView: View where RowManager: EpisodesRowManager { if onlyCurrentSeason { if let currentSeason = Array(viewModel.seasonsEpisodes.keys).first(where: { $0.id == viewModel.item.id }) { Text(currentSeason.name ?? L10n.noTitle) + .accessibility(addTraits: [.isHeader]) } } else { Menu { diff --git a/Swiftfin/Components/PillHStackView.swift b/Swiftfin/Components/PillHStackView.swift index 9b066663..f519c855 100644 --- a/Swiftfin/Components/PillHStackView.swift +++ b/Swiftfin/Components/PillHStackView.swift @@ -21,6 +21,7 @@ struct PillHStackView: View { .fontWeight(.semibold) .padding(.top, 3) .padding(.leading, 16) + .accessibility(addTraits: [.isHeader]) ScrollView(.horizontal, showsIndicators: false) { HStack { diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index 06276d2c..cbfb01f5 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -43,11 +43,12 @@ struct PortraitImageHStackView: View { selectedAction(item) } label: { VStack(alignment: horizontalAlignment) { - ImageView(src: item.imageURLContsructor(maxWidth: Int(maxWidth)), + ImageView(src: item.imageURLConstructor(maxWidth: Int(maxWidth)), bh: item.blurHash, failureInitials: item.failureInitials) .portraitPoster(width: maxWidth) .shadow(radius: 4, y: 2) + .accessibilityIgnoresInvertColors() if item.showTitle { Text(item.title) diff --git a/Swiftfin/Views/ContinueWatchingView.swift b/Swiftfin/Views/ContinueWatchingView.swift index 8c5c04c2..cd32af82 100644 --- a/Swiftfin/Views/ContinueWatchingView.swift +++ b/Swiftfin/Views/ContinueWatchingView.swift @@ -29,6 +29,7 @@ struct ContinueWatchingView: View { ZStack { ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash()) .frame(width: 320, height: 180) + .accessibilityIgnoresInvertColors() HStack { VStack { diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index e6e8f764..0d2fafeb 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -62,6 +62,7 @@ struct HomeView: View { .font(.title2) .fontWeight(.bold) .padding() + .accessibility(addTraits: [.isHeader]) } selectedAction: { item in homeRouter.route(to: \.item, item) } @@ -73,6 +74,7 @@ struct HomeView: View { .font(.title2) .fontWeight(.bold) .padding() + .accessibility(addTraits: [.isHeader]) } selectedAction: { item in homeRouter.route(to: \.item, item) } @@ -85,6 +87,7 @@ struct HomeView: View { Text(L10n.latestWithString(library.name ?? "")) .font(.title2) .fontWeight(.bold) + .accessibility(addTraits: [.isHeader]) Spacer() @@ -127,6 +130,7 @@ struct HomeView: View { homeRouter.route(to: \.settings) } label: { Image(systemName: "gearshape.fill") + .accessibilityLabel(L10n.settings) } } } diff --git a/Swiftfin/Views/ItemView/ItemViewBody.swift b/Swiftfin/Views/ItemView/ItemViewBody.swift index c7926c3b..55dfc8a5 100644 --- a/Swiftfin/Views/ItemView/ItemViewBody.swift +++ b/Swiftfin/Views/ItemView/ItemViewBody.swift @@ -55,6 +55,7 @@ struct ItemViewBody: View { L10n.seasons.text .fontWeight(.semibold) .padding() + .accessibility(addTraits: [.isHeader]) }, selectedAction: { season in itemRouter.route(to: \.item, season) }) @@ -113,6 +114,7 @@ struct ItemViewBody: View { .fontWeight(.semibold) .padding(.bottom) .padding(.horizontal) + .accessibility(addTraits: [.isHeader]) } selectedAction: { collectionItem in itemRouter.route(to: \.item, collectionItem) } @@ -128,6 +130,7 @@ struct ItemViewBody: View { .fontWeight(.semibold) .padding(.bottom) .padding(.horizontal) + .accessibility(addTraits: [.isHeader]) }, selectedAction: { person in itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title)) @@ -144,6 +147,7 @@ struct ItemViewBody: View { .fontWeight(.semibold) .padding(.bottom) .padding(.horizontal) + .accessibility(addTraits: [.isHeader]) }, selectedAction: { item in itemRouter.route(to: \.item, item) diff --git a/Swiftfin/Views/ItemView/ItemViewDetailsView.swift b/Swiftfin/Views/ItemView/ItemViewDetailsView.swift index d82df68a..a91c2058 100644 --- a/Swiftfin/Views/ItemView/ItemViewDetailsView.swift +++ b/Swiftfin/Views/ItemView/ItemViewDetailsView.swift @@ -22,6 +22,7 @@ struct ItemViewDetailsView: View { L10n.information.text .font(.title3) .fontWeight(.bold) + .accessibility(addTraits: [.isHeader]) ForEach(viewModel.informationItems, id: \.self.title) { informationItem in VStack(alignment: .leading, spacing: 2) { @@ -31,6 +32,7 @@ struct ItemViewDetailsView: View { .font(.subheadline) .foregroundColor(Color.secondary) } + .accessibilityElement(children: .combine) } } .padding(.bottom, 20) @@ -40,6 +42,7 @@ struct ItemViewDetailsView: View { L10n.media.text .font(.title3) .fontWeight(.bold) + .accessibility(addTraits: [.isHeader]) VStack(alignment: .leading, spacing: 2) { L10n.file.text @@ -48,6 +51,7 @@ struct ItemViewDetailsView: View { .font(.subheadline) .foregroundColor(Color.secondary) } + .accessibilityElement(children: .combine) VStack(alignment: .leading, spacing: 2) { L10n.containers.text @@ -56,6 +60,7 @@ struct ItemViewDetailsView: View { .font(.subheadline) .foregroundColor(Color.secondary) } + .accessibilityElement(children: .combine) ForEach(viewModel.selectedVideoPlayerViewModel?.mediaItems ?? [], id: \.self.title) { mediaItem in VStack(alignment: .leading, spacing: 2) { @@ -65,6 +70,7 @@ struct ItemViewDetailsView: View { .font(.subheadline) .foregroundColor(Color.secondary) } + .accessibilityElement(children: .combine) } } } diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift index c4290d05..8ef58c0e 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -28,6 +28,7 @@ struct ItemLandscapeMainView: View { bh: viewModel.item.getPrimaryImageBlurHash()) .frame(width: 130, height: 195) .cornerRadius(10) + .accessibilityIgnoresInvertColors() Spacer().frame(height: 15) @@ -100,6 +101,7 @@ struct ItemLandscapeMainView: View { .edgesIgnoringSafeArea(.all) .blur(radius: 8) .layoutPriority(-1) + .accessibilityIgnoresInvertColors() // iPadOS is making the view go all the way to the edge. // We have to accomodate this here diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift index 7a58de68..6ee32c2a 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift @@ -25,6 +25,7 @@ struct ItemLandscapeTopBarView: View { .foregroundColor(.primary) .padding(.leading, 16) .padding(.bottom, 10) + .accessibility(addTraits: [.isHeader]) // MARK: Details diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift index 2ae40695..db91e1c6 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift @@ -26,6 +26,7 @@ struct PortraitHeaderOverlayView: View { ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130)) .portraitPoster(width: 130) + .accessibilityIgnoresInvertColors() VStack(alignment: .leading, spacing: 1) { Spacer() diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift index 0c6403b1..fbb4692b 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift @@ -23,6 +23,7 @@ struct ItemPortraitMainView: View { bh: viewModel.item.getBackdropImageBlurHash()) .opacity(0.4) .blur(radius: 2.0) + .accessibilityIgnoresInvertColors() } // MARK: portraitStaticOverlayView diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index 2438e35c..26c25fb6 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -82,6 +82,7 @@ struct LibraryListView: View { ZStack { ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) .opacity(0.4) + .accessibilityIgnoresInvertColors() HStack { Spacer() VStack { diff --git a/Swiftfin/Views/ServerListView.swift b/Swiftfin/Views/ServerListView.swift index 79725c0f..e561acd4 100644 --- a/Swiftfin/Views/ServerListView.swift +++ b/Swiftfin/Views/ServerListView.swift @@ -104,6 +104,7 @@ struct ServerListView: View { serverListRouter.route(to: \.basicAppSettings) } label: { Image(systemName: "gearshape.fill") + .accessibilityLabel(L10n.settings) } } diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index dfcf1611..bbefa2f9 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -102,6 +102,7 @@ class VLCPlayerViewController: UIViewController { setupConstraints() view.backgroundColor = .black + view.accessibilityIgnoresInvertColors = true setupMediaPlayer(newViewModel: viewModel) diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index efcdd6f7..a1457c89 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ