Merge branch 'main' into chapter-support
This commit is contained in:
commit
6b7f3672b2
|
@ -85,5 +85,6 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeSettingsTab(isActive: Bool) -> some View {
|
func makeSettingsTab(isActive: Bool) -> some View {
|
||||||
Image(systemName: "gearshape.fill")
|
Image(systemName: "gearshape.fill")
|
||||||
|
.accessibilityLabel(L10n.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ extension BaseItemDto: PortraitImageStackable {
|
||||||
id ?? "no id"
|
id ?? "no id"
|
||||||
}
|
}
|
||||||
|
|
||||||
public func imageURLContsructor(maxWidth: Int) -> URL {
|
public func imageURLConstructor(maxWidth: Int) -> URL {
|
||||||
switch self.itemType {
|
switch self.itemType {
|
||||||
case .episode:
|
case .episode:
|
||||||
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
||||||
|
|
|
@ -68,7 +68,7 @@ extension BaseItemPerson: PortraitImageStackable {
|
||||||
(id ?? "noid") + title + (subtitle ?? "nodescription") + blurHash + failureInitials
|
(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)
|
self.getImage(baseURL: SessionManager.main.currentLogin.server.currentURI, maxWidth: maxWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -356,6 +356,8 @@ internal enum L10n {
|
||||||
internal static var system: String { return L10n.tr("Localizable", "system") }
|
internal static var system: String { return L10n.tr("Localizable", "system") }
|
||||||
/// Tags
|
/// Tags
|
||||||
internal static var tags: String { return L10n.tr("Localizable", "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
|
/// Try again
|
||||||
internal static var tryAgain: String { return L10n.tr("Localizable", "tryAgain") }
|
internal static var tryAgain: String { return L10n.tr("Localizable", "tryAgain") }
|
||||||
/// TV Shows
|
/// TV Shows
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol PortraitImageStackable {
|
public protocol PortraitImageStackable {
|
||||||
func imageURLContsructor(maxWidth: Int) -> URL
|
func imageURLConstructor(maxWidth: Int) -> URL
|
||||||
var title: String { get }
|
var title: String { get }
|
||||||
var subtitle: String? { get }
|
var subtitle: String? { get }
|
||||||
var blurHash: String { get }
|
var blurHash: String { get }
|
||||||
|
|
|
@ -44,7 +44,8 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectToServer(uri: String) {
|
func connectToServer(uri: String, redirectCount: Int = 0) {
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
var uri = uri
|
var uri = uri
|
||||||
if uri == "localhost" {
|
if uri == "localhost" {
|
||||||
|
@ -63,6 +64,27 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
case .finished: ()
|
case .finished: ()
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
switch 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:
|
case is SwiftfinStore.Errors:
|
||||||
let swiftfinError = error as! SwiftfinStore.Errors
|
let swiftfinError = error as! SwiftfinStore.Errors
|
||||||
switch swiftfinError {
|
switch swiftfinError {
|
||||||
|
|
|
@ -37,6 +37,7 @@ struct ImageView: View {
|
||||||
Text(failureInitials)
|
Text(failureInitials)
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
.accessibilityHidden(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ struct EpisodeRowCard: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(episode.getEpisodeLocator() ?? "S-:E-")
|
Text(episode.getEpisodeLocator() ?? "S-:E-")
|
||||||
|
|
|
@ -25,6 +25,7 @@ struct EpisodesRowView<RowManager>: View where RowManager: EpisodesRowManager {
|
||||||
if onlyCurrentSeason {
|
if onlyCurrentSeason {
|
||||||
if let currentSeason = Array(viewModel.seasonsEpisodes.keys).first(where: { $0.id == viewModel.item.id }) {
|
if let currentSeason = Array(viewModel.seasonsEpisodes.keys).first(where: { $0.id == viewModel.item.id }) {
|
||||||
Text(currentSeason.name ?? L10n.noTitle)
|
Text(currentSeason.name ?? L10n.noTitle)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Menu {
|
Menu {
|
||||||
|
|
|
@ -21,6 +21,7 @@ struct PillHStackView<ItemType: PillStackable>: View {
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.top, 3)
|
.padding(.top, 3)
|
||||||
.padding(.leading, 16)
|
.padding(.leading, 16)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
|
@ -43,11 +43,12 @@ struct PortraitImageHStackView<TopBarView: View, ItemType: PortraitImageStackabl
|
||||||
selectedAction(item)
|
selectedAction(item)
|
||||||
} label: {
|
} label: {
|
||||||
VStack(alignment: horizontalAlignment) {
|
VStack(alignment: horizontalAlignment) {
|
||||||
ImageView(src: item.imageURLContsructor(maxWidth: Int(maxWidth)),
|
ImageView(src: item.imageURLConstructor(maxWidth: Int(maxWidth)),
|
||||||
bh: item.blurHash,
|
bh: item.blurHash,
|
||||||
failureInitials: item.failureInitials)
|
failureInitials: item.failureInitials)
|
||||||
.portraitPoster(width: maxWidth)
|
.portraitPoster(width: maxWidth)
|
||||||
.shadow(radius: 4, y: 2)
|
.shadow(radius: 4, y: 2)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
if item.showTitle {
|
if item.showTitle {
|
||||||
Text(item.title)
|
Text(item.title)
|
||||||
|
|
|
@ -35,11 +35,12 @@ struct PortraitItemButton<ItemType: PortraitImageStackable>: View {
|
||||||
selectedAction(item)
|
selectedAction(item)
|
||||||
} label: {
|
} label: {
|
||||||
VStack(alignment: horizontalAlignment) {
|
VStack(alignment: horizontalAlignment) {
|
||||||
ImageView(src: item.imageURLContsructor(maxWidth: Int(maxWidth)),
|
ImageView(src: item.imageURLConstructor(maxWidth: Int(maxWidth)),
|
||||||
bh: item.blurHash,
|
bh: item.blurHash,
|
||||||
failureInitials: item.failureInitials)
|
failureInitials: item.failureInitials)
|
||||||
.portraitPoster(width: maxWidth)
|
.portraitPoster(width: maxWidth)
|
||||||
.shadow(radius: 4, y: 2)
|
.shadow(radius: 4, y: 2)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
if item.showTitle {
|
if item.showTitle {
|
||||||
Text(item.title)
|
Text(item.title)
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct ContinueWatchingView: View {
|
||||||
ZStack {
|
ZStack {
|
||||||
ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
||||||
.frame(width: 320, height: 180)
|
.frame(width: 320, height: 180)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
|
@ -62,6 +62,7 @@ struct HomeView: View {
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.padding()
|
.padding()
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
} selectedAction: { item in
|
} selectedAction: { item in
|
||||||
homeRouter.route(to: \.item, item)
|
homeRouter.route(to: \.item, item)
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,7 @@ struct HomeView: View {
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.padding()
|
.padding()
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
} selectedAction: { item in
|
} selectedAction: { item in
|
||||||
homeRouter.route(to: \.item, item)
|
homeRouter.route(to: \.item, item)
|
||||||
}
|
}
|
||||||
|
@ -85,6 +87,7 @@ struct HomeView: View {
|
||||||
Text(L10n.latestWithString(library.name ?? ""))
|
Text(L10n.latestWithString(library.name ?? ""))
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -127,6 +130,7 @@ struct HomeView: View {
|
||||||
homeRouter.route(to: \.settings)
|
homeRouter.route(to: \.settings)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "gearshape.fill")
|
Image(systemName: "gearshape.fill")
|
||||||
|
.accessibilityLabel(L10n.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ struct ItemViewBody: View {
|
||||||
L10n.seasons.text
|
L10n.seasons.text
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding()
|
.padding()
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
}, selectedAction: { season in
|
}, selectedAction: { season in
|
||||||
itemRouter.route(to: \.item, season)
|
itemRouter.route(to: \.item, season)
|
||||||
})
|
})
|
||||||
|
@ -113,6 +114,7 @@ struct ItemViewBody: View {
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
} selectedAction: { collectionItem in
|
} selectedAction: { collectionItem in
|
||||||
itemRouter.route(to: \.item, collectionItem)
|
itemRouter.route(to: \.item, collectionItem)
|
||||||
}
|
}
|
||||||
|
@ -128,6 +130,7 @@ struct ItemViewBody: View {
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
},
|
},
|
||||||
selectedAction: { person in
|
selectedAction: { person in
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
||||||
|
@ -144,6 +147,7 @@ struct ItemViewBody: View {
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
},
|
},
|
||||||
selectedAction: { item in
|
selectedAction: { item in
|
||||||
itemRouter.route(to: \.item, item)
|
itemRouter.route(to: \.item, item)
|
||||||
|
|
|
@ -22,6 +22,7 @@ struct ItemViewDetailsView: View {
|
||||||
L10n.information.text
|
L10n.information.text
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
|
|
||||||
ForEach(viewModel.informationItems, id: \.self.title) { informationItem in
|
ForEach(viewModel.informationItems, id: \.self.title) { informationItem in
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
@ -31,6 +32,7 @@ struct ItemViewDetailsView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundColor(Color.secondary)
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, 20)
|
.padding(.bottom, 20)
|
||||||
|
@ -40,6 +42,7 @@ struct ItemViewDetailsView: View {
|
||||||
L10n.media.text
|
L10n.media.text
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
L10n.file.text
|
L10n.file.text
|
||||||
|
@ -48,6 +51,7 @@ struct ItemViewDetailsView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundColor(Color.secondary)
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
L10n.containers.text
|
L10n.containers.text
|
||||||
|
@ -56,6 +60,7 @@ struct ItemViewDetailsView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundColor(Color.secondary)
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
|
|
||||||
ForEach(viewModel.selectedVideoPlayerViewModel?.mediaItems ?? [], id: \.self.title) { mediaItem in
|
ForEach(viewModel.selectedVideoPlayerViewModel?.mediaItems ?? [], id: \.self.title) { mediaItem in
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
@ -65,6 +70,7 @@ struct ItemViewDetailsView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundColor(Color.secondary)
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ struct ItemLandscapeMainView: View {
|
||||||
bh: viewModel.item.getPrimaryImageBlurHash())
|
bh: viewModel.item.getPrimaryImageBlurHash())
|
||||||
.frame(width: 130, height: 195)
|
.frame(width: 130, height: 195)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
Spacer().frame(height: 15)
|
Spacer().frame(height: 15)
|
||||||
|
|
||||||
|
@ -100,6 +101,7 @@ struct ItemLandscapeMainView: View {
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.blur(radius: 8)
|
.blur(radius: 8)
|
||||||
.layoutPriority(-1)
|
.layoutPriority(-1)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
// iPadOS is making the view go all the way to the edge.
|
// iPadOS is making the view go all the way to the edge.
|
||||||
// We have to accomodate this here
|
// We have to accomodate this here
|
||||||
|
|
|
@ -25,6 +25,7 @@ struct ItemLandscapeTopBarView: View {
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
.padding(.leading, 16)
|
.padding(.leading, 16)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
|
.accessibility(addTraits: [.isHeader])
|
||||||
|
|
||||||
// MARK: Details
|
// MARK: Details
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ struct PortraitHeaderOverlayView: View {
|
||||||
|
|
||||||
ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130))
|
ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130))
|
||||||
.portraitPoster(width: 130)
|
.portraitPoster(width: 130)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 1) {
|
VStack(alignment: .leading, spacing: 1) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
|
@ -23,6 +23,7 @@ struct ItemPortraitMainView: View {
|
||||||
bh: viewModel.item.getBackdropImageBlurHash())
|
bh: viewModel.item.getBackdropImageBlurHash())
|
||||||
.opacity(0.4)
|
.opacity(0.4)
|
||||||
.blur(radius: 2.0)
|
.blur(radius: 2.0)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: portraitStaticOverlayView
|
// MARK: portraitStaticOverlayView
|
||||||
|
|
|
@ -82,6 +82,7 @@ struct LibraryListView: View {
|
||||||
ZStack {
|
ZStack {
|
||||||
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||||
.opacity(0.4)
|
.opacity(0.4)
|
||||||
|
.accessibilityIgnoresInvertColors()
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
VStack {
|
VStack {
|
||||||
|
|
|
@ -104,6 +104,7 @@ struct ServerListView: View {
|
||||||
serverListRouter.route(to: \.basicAppSettings)
|
serverListRouter.route(to: \.basicAppSettings)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "gearshape.fill")
|
Image(systemName: "gearshape.fill")
|
||||||
|
.accessibilityLabel(L10n.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ class VLCPlayerViewController: UIViewController {
|
||||||
setupConstraints()
|
setupConstraints()
|
||||||
|
|
||||||
view.backgroundColor = .black
|
view.backgroundColor = .black
|
||||||
|
view.accessibilityIgnoresInvertColors = true
|
||||||
|
|
||||||
setupMediaPlayer(newViewModel: viewModel)
|
setupMediaPlayer(newViewModel: viewModel)
|
||||||
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue