mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-12-02 19:16:27 +00:00
Merge branch 'main' into multiple-media-sources
This commit is contained in:
commit
cdc294dd96
@ -224,6 +224,8 @@ internal enum L10n {
|
||||
internal static let playbackSettings = L10n.tr("Localizable", "playbackSettings")
|
||||
/// Playback Speed
|
||||
internal static let playbackSpeed = L10n.tr("Localizable", "playbackSpeed")
|
||||
/// Play From Beginning
|
||||
internal static let playFromBeginning = L10n.tr("Localizable", "playFromBeginning")
|
||||
/// Play Next
|
||||
internal static let playNext = L10n.tr("Localizable", "playNext")
|
||||
/// Play Next Item
|
||||
@ -252,6 +254,8 @@ internal enum L10n {
|
||||
internal static let remove = L10n.tr("Localizable", "remove")
|
||||
/// Remove All Users
|
||||
internal static let removeAllUsers = L10n.tr("Localizable", "removeAllUsers")
|
||||
/// Remove From Resume
|
||||
internal static let removeFromResume = L10n.tr("Localizable", "removeFromResume")
|
||||
/// Reset
|
||||
internal static let reset = L10n.tr("Localizable", "reset")
|
||||
/// Reset App Settings
|
||||
|
@ -70,19 +70,3 @@ extension EpisodesRowManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class SingleSeasonEpisodesRowViewModel: ViewModel {
|
||||
|
||||
// TODO: Protocol these viewmodels for generalization instead of Season
|
||||
|
||||
@ObservedObject
|
||||
var seasonItemViewModel: SeasonItemViewModel
|
||||
@Published
|
||||
var episodes: [BaseItemDto]
|
||||
|
||||
init(seasonItemViewModel: SeasonItemViewModel) {
|
||||
self.seasonItemViewModel = seasonItemViewModel
|
||||
self.episodes = seasonItemViewModel.episodes
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
@ -185,6 +185,20 @@ final class HomeViewModel: ViewModel {
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func removeItemFromResume(_ item: BaseItemDto) {
|
||||
guard let itemID = item.id, resumeItems.contains(where: { $0.id == itemID }) else { return }
|
||||
|
||||
PlaystateAPI.markUnplayedItem(userId: SessionManager.main.currentLogin.user.id,
|
||||
itemId: item.id!)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
}, receiveValue: { _ in
|
||||
self.refreshResumeItems()
|
||||
self.refreshNextUpItems()
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: Next Up Items
|
||||
|
||||
private func refreshNextUpItems() {
|
||||
|
@ -103,7 +103,7 @@ final class VideoPlayerViewModel: ViewModel {
|
||||
|
||||
// MARK: General
|
||||
|
||||
let item: BaseItemDto
|
||||
private(set) var item: BaseItemDto
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
let streamURL: URL
|
||||
@ -249,6 +249,22 @@ final class VideoPlayerViewModel: ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Injected Values
|
||||
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
// Injects custom values that override certain settings
|
||||
func injectCustomValues(startFromBeginning: Bool = false) {
|
||||
|
||||
if startFromBeginning {
|
||||
item.userData?.playbackPositionTicks = 0
|
||||
item.userData?.playedPercentage = 0
|
||||
sliderPercentage = 0
|
||||
sliderPercentageChanged(newValue: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Adjacent Items
|
||||
|
||||
extension VideoPlayerViewModel {
|
||||
|
@ -6,6 +6,7 @@
|
||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import NukeUI
|
||||
import SwiftUI
|
||||
|
||||
struct ImageView: View {
|
||||
@ -40,17 +41,12 @@ struct ImageView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
AsyncImage(url: source, transaction: Transaction(animation: .easeInOut)) { phase in
|
||||
switch phase {
|
||||
case let .success(image):
|
||||
LazyImage(source: source) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
case .failure:
|
||||
} else if state.error != nil {
|
||||
failureImage
|
||||
default:
|
||||
// TODO: remove once placeholder hash image fixed
|
||||
|
||||
} else {
|
||||
#if os(tvOS)
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
@ -66,5 +62,6 @@ struct ImageView: View {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.pipeline(ImagePipeline(configuration: .withDataCache))
|
||||
}
|
||||
}
|
||||
|
@ -17,45 +17,47 @@ struct EpisodeRowCard: View {
|
||||
let episode: BaseItemDto
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
itemRouter.route(to: \.item, episode)
|
||||
} label: {
|
||||
HStack(alignment: .top) {
|
||||
VStack {
|
||||
Button {
|
||||
itemRouter.route(to: \.item, episode)
|
||||
} label: {
|
||||
ImageView(src: episode.getBackdropImage(maxWidth: 550),
|
||||
bh: episode.getBackdropImageBlurHash())
|
||||
.mask(Rectangle().frame(width: 550, height: 308))
|
||||
.frame(width: 550, height: 308)
|
||||
}
|
||||
.buttonStyle(CardButtonStyle())
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(episode.getEpisodeLocator() ?? "")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(episode.name ?? "")
|
||||
.font(.footnote)
|
||||
.padding(.bottom, 1)
|
||||
|
||||
ImageView(src: episode.getBackdropImage(maxWidth: 500),
|
||||
bh: episode.getBackdropImageBlurHash())
|
||||
.mask(Rectangle().frame(width: 500, height: 280))
|
||||
.frame(width: 500, height: 280)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(episode.getEpisodeLocator() ?? "")
|
||||
if episode.unaired {
|
||||
Text(episode.airDateLabel ?? L10n.noOverviewAvailable)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(episode.name ?? "")
|
||||
.font(.footnote)
|
||||
.padding(.bottom, 1)
|
||||
|
||||
if episode.unaired {
|
||||
Text(episode.airDateLabel ?? L10n.noOverviewAvailable)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
.lineLimit(3)
|
||||
} else {
|
||||
Text(episode.overview ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.light)
|
||||
.lineLimit(4)
|
||||
}
|
||||
.fontWeight(.light)
|
||||
.lineLimit(3)
|
||||
} else {
|
||||
Text(episode.overview ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.light)
|
||||
.lineLimit(4)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: 500)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.frame(width: 550)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.focusSection()
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ struct CinematicResumeCardView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
var homeRouter: HomeCoordinator.Router
|
||||
@ObservedObject
|
||||
var viewModel: HomeViewModel
|
||||
let item: BaseItemDto
|
||||
|
||||
var body: some View {
|
||||
@ -55,6 +57,13 @@ struct CinematicResumeCardView: View {
|
||||
}
|
||||
.buttonStyle(CardButtonStyle())
|
||||
.padding(.top)
|
||||
.contextMenu {
|
||||
Button(role: .destructive) {
|
||||
viewModel.removeItemFromResume(item)
|
||||
} label: {
|
||||
L10n.removeFromResume.text
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ struct HomeCinematicView: View {
|
||||
|
||||
@FocusState
|
||||
var selectedItem: BaseItemDto?
|
||||
@ObservedObject
|
||||
var viewModel: HomeViewModel
|
||||
@State
|
||||
private var updatedSelectedItem: BaseItemDto?
|
||||
@State
|
||||
@ -41,7 +43,8 @@ struct HomeCinematicView: View {
|
||||
private let items: [HomeCinematicViewItem]
|
||||
private let backgroundViewModel = DynamicCinematicBackgroundViewModel()
|
||||
|
||||
init(items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) {
|
||||
init(viewModel: HomeViewModel, items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) {
|
||||
self.viewModel = viewModel
|
||||
self.items = items
|
||||
self.forcedItemSubtitle = forcedItemSubtitle
|
||||
}
|
||||
@ -99,7 +102,7 @@ struct HomeCinematicView: View {
|
||||
CinematicNextUpCardView(item: item.item, showOverlay: true)
|
||||
.focused($selectedItem, equals: item.item)
|
||||
case .resume:
|
||||
CinematicResumeCardView(item: item.item)
|
||||
CinematicResumeCardView(viewModel: viewModel, item: item.item)
|
||||
.focused($selectedItem, equals: item.item)
|
||||
case .plain:
|
||||
CinematicNextUpCardView(item: item.item, showOverlay: false)
|
||||
|
@ -15,7 +15,7 @@ struct HomeView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
var homeRouter: HomeCoordinator.Router
|
||||
@ObservedObject
|
||||
@StateObject
|
||||
var viewModel = HomeViewModel()
|
||||
@Default(.showPosterLabels)
|
||||
var showPosterLabels
|
||||
@ -32,7 +32,8 @@ struct HomeView: View {
|
||||
LazyVStack(alignment: .leading) {
|
||||
|
||||
if viewModel.resumeItems.isEmpty {
|
||||
HomeCinematicView(items: viewModel.latestAddedItems.map { .init(item: $0, type: .plain) },
|
||||
HomeCinematicView(viewModel: viewModel,
|
||||
items: viewModel.latestAddedItems.map { .init(item: $0, type: .plain) },
|
||||
forcedItemSubtitle: L10n.recentlyAdded)
|
||||
|
||||
if !viewModel.nextUpItems.isEmpty {
|
||||
@ -40,7 +41,8 @@ struct HomeView: View {
|
||||
.focusSection()
|
||||
}
|
||||
} else {
|
||||
HomeCinematicView(items: viewModel.resumeItems.map { .init(item: $0, type: .resume) })
|
||||
HomeCinematicView(viewModel: viewModel,
|
||||
items: viewModel.resumeItems.map { .init(item: $0, type: .resume) })
|
||||
|
||||
if !viewModel.nextUpItems.isEmpty {
|
||||
NextUpView(items: viewModel.nextUpItems)
|
||||
|
@ -78,6 +78,24 @@ struct CinematicItemViewTopRow: View {
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.buttonStyle(CardButtonStyle())
|
||||
.contextMenu {
|
||||
if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 {
|
||||
Button {
|
||||
if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel {
|
||||
itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
||||
itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel)
|
||||
} else {
|
||||
LogManager.shared.log.error("Attempted to play item but no playback information available")
|
||||
}
|
||||
} label: {
|
||||
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
||||
}
|
||||
|
||||
Button(role: .cancel) {} label: {
|
||||
L10n.cancel.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,6 +281,7 @@
|
||||
E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
||||
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; };
|
||||
E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; };
|
||||
E11D83AF278FA998006E9776 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E11D83AE278FA998006E9776 /* NukeUI */; };
|
||||
E12186DE2718F1C50010884C /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E12186DD2718F1C50010884C /* Defaults */; };
|
||||
E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; };
|
||||
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; };
|
||||
@ -291,6 +292,7 @@
|
||||
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
|
||||
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
|
||||
E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
|
||||
E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E1361DA6278FA7A300BEC523 /* NukeUI */; };
|
||||
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1384943278036C70024FB48 /* VLCPlayerViewController.swift */; };
|
||||
E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */; };
|
||||
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3BE27163DD7009D4DAF /* AppDelegate.swift */; };
|
||||
@ -779,6 +781,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */,
|
||||
E11D83AF278FA998006E9776 /* NukeUI in Frameworks */,
|
||||
E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */,
|
||||
6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */,
|
||||
535870912669D7A800D05A09 /* Introspect in Frameworks */,
|
||||
@ -799,6 +802,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E13DD3D327168E65009D4DAF /* Defaults in Frameworks */,
|
||||
E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */,
|
||||
53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */,
|
||||
E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
|
||||
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
||||
@ -1714,6 +1718,7 @@
|
||||
E1A9999A271A343C008E78C0 /* SwiftUICollection */,
|
||||
E178857C278037FD0094FBCF /* JellyfinAPI */,
|
||||
E1AE8E7D2789136D00FBDDAA /* Nuke */,
|
||||
E11D83AE278FA998006E9776 /* NukeUI */,
|
||||
);
|
||||
productName = "JellyfinPlayer tvOS";
|
||||
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
|
||||
@ -1752,6 +1757,7 @@
|
||||
E10EAA44277BB646000269ED /* JellyfinAPI */,
|
||||
E10EAA4C277BB716000269ED /* Sliders */,
|
||||
E1AE8E7B2789135A00FBDDAA /* Nuke */,
|
||||
E1361DA6278FA7A300BEC523 /* NukeUI */,
|
||||
);
|
||||
productName = JellyfinPlayer;
|
||||
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
|
||||
@ -1844,6 +1850,7 @@
|
||||
E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
|
||||
E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */,
|
||||
E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||
E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */,
|
||||
);
|
||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -3024,6 +3031,14 @@
|
||||
minimumVersion = 1.0.0;
|
||||
};
|
||||
};
|
||||
E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kean/NukeUI";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.7.0;
|
||||
};
|
||||
};
|
||||
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/JohnEstropia/CoreStore.git";
|
||||
@ -3044,8 +3059,8 @@
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kean/Nuke";
|
||||
requirement = {
|
||||
kind = upToNextMinorVersion;
|
||||
minimumVersion = 9.6.0;
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 10.0.0;
|
||||
};
|
||||
};
|
||||
E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
|
||||
@ -3129,6 +3144,11 @@
|
||||
package = E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */;
|
||||
productName = Sliders;
|
||||
};
|
||||
E11D83AE278FA998006E9776 /* NukeUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
E12186DD2718F1C50010884C /* Defaults */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;
|
||||
@ -3139,6 +3159,11 @@
|
||||
package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */;
|
||||
productName = CombineExt;
|
||||
};
|
||||
E1361DA6278FA7A300BEC523 /* NukeUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
E13DD3C52716499E009D4DAF /* CoreStore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
|
||||
|
@ -24,8 +24,8 @@
|
||||
"repositoryURL": "https://github.com/CombineCommunity/CombineExt",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "0880829102152185190064fd17847a7c681d2127",
|
||||
"version": "1.5.1"
|
||||
"revision": "8ca006df5e3cc6bb176b70238e2b0014bbc3a235",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -42,8 +42,17 @@
|
||||
"repositoryURL": "https://github.com/sindresorhus/Defaults",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "55f3302c3ab30a8760f10042d0ebc0a6907f865a",
|
||||
"version": "6.1.0"
|
||||
"revision": "8a6e4a96fd38504a05903d136c85634b65fd7c4d",
|
||||
"version": "6.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Gifu",
|
||||
"repositoryURL": "https://github.com/kaishin/Gifu",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "51f2eab32903e336f590c013267cfa4d7f8b06c4",
|
||||
"version": "3.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -60,8 +69,17 @@
|
||||
"repositoryURL": "https://github.com/kean/Nuke",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "7f73ceaeacd5df75a7994cd82e165ad9ff1815db",
|
||||
"version": "9.6.1"
|
||||
"revision": "6be3e778f1663b16dd645b7e8a0a01f73b5ed7f3",
|
||||
"version": "10.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "NukeUI",
|
||||
"repositoryURL": "https://github.com/kean/NukeUI",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "08e953d8d80b409bebcd95ba0635fdd748934ce0",
|
||||
"version": "0.7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -78,8 +96,8 @@
|
||||
"repositoryURL": "https://github.com/sushichop/Puppy",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "95ce04b0e778b8d7c351876bc98bbf68328dfc9b",
|
||||
"version": "0.3.1"
|
||||
"revision": "dc82e65c749cee431ffbb8c0913680b61ccd7e08",
|
||||
"version": "0.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -81,6 +81,13 @@ struct ContinueWatchingView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button(role: .destructive) {
|
||||
viewModel.removeItemFromResume(item)
|
||||
} label: {
|
||||
L10n.removeFromResume.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
@ -50,6 +50,20 @@ struct ItemLandscapeMainView: View {
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.disabled(viewModel.playButtonItem == nil || viewModel.selectedVideoPlayerViewModel == nil)
|
||||
.contextMenu {
|
||||
if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 {
|
||||
Button {
|
||||
if let itemVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
|
||||
itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
||||
itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel)
|
||||
} else {
|
||||
LogManager.shared.log.error("Attempted to play item but no playback information available")
|
||||
}
|
||||
} label: {
|
||||
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
@ -113,7 +113,11 @@ struct PortraitHeaderOverlayView: View {
|
||||
// MARK: Play
|
||||
|
||||
Button {
|
||||
self.itemRouter.route(to: \.videoPlayer, viewModel.selectedVideoPlayerViewModel!)
|
||||
if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel {
|
||||
itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel)
|
||||
} else {
|
||||
LogManager.shared.log.error("Attempted to play item but no playback information available")
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "play.fill")
|
||||
@ -128,7 +132,21 @@ struct PortraitHeaderOverlayView: View {
|
||||
.background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.disabled(viewModel.playButtonItem == nil || viewModel.selectedVideoPlayerViewModel == nil)
|
||||
.disabled(viewModel.playButtonItem == nil)
|
||||
.contextMenu {
|
||||
if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 {
|
||||
Button {
|
||||
if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel {
|
||||
itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
|
||||
itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel)
|
||||
} else {
|
||||
LogManager.shared.log.error("Attempted to play item but no playback information available")
|
||||
}
|
||||
} label: {
|
||||
Label(L10n.playFromBeginning, systemImage: "gobackward")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user