ResumeItem class to struct
LibraryView NavigationLink Recovery
This commit is contained in:
parent
7ada918ea5
commit
3b7778b3cf
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1250"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||||
|
BuildableName = "JellyfinPlayer.app"
|
||||||
|
BlueprintName = "JellyfinPlayer"
|
||||||
|
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||||
|
BuildableName = "JellyfinPlayer.app"
|
||||||
|
BlueprintName = "JellyfinPlayer"
|
||||||
|
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||||
|
BuildableName = "JellyfinPlayer.app"
|
||||||
|
BlueprintName = "JellyfinPlayer"
|
||||||
|
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -26,8 +26,11 @@ enum ItemType: String {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SortType: String {
|
enum SortType: String {
|
||||||
case name = "Name"
|
case name = "SortName"
|
||||||
case dateCreated = "DateCreated"
|
case dateCreated = "DateCreated"
|
||||||
|
case datePlayed = "DatePlayed"
|
||||||
|
case premiereDate = "PremiereDate"
|
||||||
|
case runtime = "Runtime"
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ASC: String {
|
enum ASC: String {
|
||||||
|
@ -37,20 +40,22 @@ enum ASC: String {
|
||||||
|
|
||||||
enum FilterType: String {
|
enum FilterType: String {
|
||||||
case isFavorite = "IsFavorite"
|
case isFavorite = "IsFavorite"
|
||||||
|
case isUnplayed = "IsUnplayed"
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Filter {
|
struct Filter {
|
||||||
var imageTypes = [ImageType]()
|
var imageTypes: [ImageType] = [.primary, .backdrop, .thumb, .banner]
|
||||||
var fields = [Field]()
|
var fields: [Field] = [.primaryImageAspectRatio, .basicSyncInfo]
|
||||||
var itemTypes = [ItemType]()
|
var itemTypes: [ItemType] = [.movie, .series]
|
||||||
var filterTypes = [FilterType]()
|
var filterTypes = [FilterType]()
|
||||||
var sort: SortType?
|
var sort: SortType? = .dateCreated
|
||||||
var asc: ASC?
|
var asc: ASC? = .descending
|
||||||
var parentID: String?
|
var parentID: String?
|
||||||
var imageTypeLimit: Int?
|
var imageTypeLimit: Int? = 1
|
||||||
var recursive = true
|
var recursive = true
|
||||||
var genres = [String]()
|
var genres = [String]()
|
||||||
var personIds = [String]()
|
var personIds = [String]()
|
||||||
|
var officialRatings = [String]()
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Filter {
|
extension Filter {
|
||||||
|
@ -67,6 +72,7 @@ extension Filter {
|
||||||
parameters["SortOrder"] = asc?.rawValue
|
parameters["SortOrder"] = asc?.rawValue
|
||||||
parameters["Genres"] = genres.joined(separator: ",")
|
parameters["Genres"] = genres.joined(separator: ",")
|
||||||
parameters["PersonIds"] = personIds.joined(separator: ",")
|
parameters["PersonIds"] = personIds.joined(separator: ",")
|
||||||
|
parameters["OfficialRatings"] = officialRatings.joined(separator: ",")
|
||||||
return parameters
|
return parameters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +131,9 @@ extension JellyfinAPI: TargetType {
|
||||||
case let .items(global, _, _),
|
case let .items(global, _, _),
|
||||||
let .search(global, _, _, _):
|
let .search(global, _, _, _):
|
||||||
return [
|
return [
|
||||||
"X-Emby-Authorization": global.authHeader
|
"X-Emby-Authorization": global.authHeader,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,9 +197,9 @@ struct ContentView: View {
|
||||||
HStack() {
|
HStack() {
|
||||||
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
||||||
Spacer()
|
Spacer()
|
||||||
// NavigationLink(destination: LibraryView(prefill: library_id, names: [library_id: library_names[library_id] ?? ""], libraries: [library_id], filter: "&SortBy=DateCreated&SortOrder=Descending")) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(parentID: library_id)), title: library_names[library_id] ?? "")) {
|
||||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||||
// }
|
}
|
||||||
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||||
LatestMediaView(library: library_id)
|
LatestMediaView(library: library_id)
|
||||||
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||||
|
|
|
@ -64,7 +64,7 @@ struct ContinueWatchingView: View {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
for (_,item):(String, JSON) in json["Items"] {
|
for (_,item):(String, JSON) in json["Items"] {
|
||||||
// Do something you want
|
// Do something you want
|
||||||
let itemObj = ResumeItem()
|
var itemObj = ResumeItem()
|
||||||
if(item["PrimaryImageAspectRatio"].double ?? 0.0 < 1.0) {
|
if(item["PrimaryImageAspectRatio"].double ?? 0.0 < 1.0) {
|
||||||
//portrait; use backdrop instead
|
//portrait; use backdrop instead
|
||||||
itemObj.Image = item["BackdropImageTags"][0].string ?? ""
|
itemObj.Image = item["BackdropImageTags"][0].string ?? ""
|
||||||
|
|
|
@ -12,9 +12,9 @@ import Moya
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
final class LibraryViewModel: ObservableObject {
|
final class LibraryViewModel: ObservableObject {
|
||||||
fileprivate var provider = MoyaProvider<JellyfinAPI>(plugins: [NetworkLoggerPlugin(configuration: NetworkLoggerPlugin.Configuration(logOptions: .verbose))])
|
fileprivate var provider =
|
||||||
|
MoyaProvider<JellyfinAPI>(plugins: [NetworkLoggerPlugin()])
|
||||||
|
|
||||||
var prefillID: String
|
|
||||||
@Published
|
@Published
|
||||||
var filter: Filter
|
var filter: Filter
|
||||||
|
|
||||||
|
@ -31,64 +31,59 @@ final class LibraryViewModel: ObservableObject {
|
||||||
|
|
||||||
var page = 1
|
var page = 1
|
||||||
|
|
||||||
var globalData = GlobalData()
|
var globalData = GlobalData() {
|
||||||
|
didSet {
|
||||||
|
injectEnvironmentData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate var cancellables = Set<AnyCancellable>()
|
fileprivate var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(prefillID: String,
|
init(filter: Filter = Filter()) {
|
||||||
filter: Filter? = nil)
|
self.filter = filter
|
||||||
{
|
|
||||||
self.prefillID = prefillID
|
|
||||||
|
|
||||||
if let unwrappedFilter = filter {
|
|
||||||
self.filter = unwrappedFilter
|
|
||||||
} else {
|
|
||||||
self.filter = Filter(imageTypes: [.primary, .backdrop, .thumb, .banner],
|
|
||||||
fields: [.primaryImageAspectRatio, .basicSyncInfo],
|
|
||||||
itemTypes: [.movie, .series],
|
|
||||||
sort: .dateCreated,
|
|
||||||
asc: .descending,
|
|
||||||
parentID: prefillID,
|
|
||||||
imageTypeLimit: 1,
|
|
||||||
recursive: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func injectEnvironmentData() {
|
||||||
|
cancellables.removeAll()
|
||||||
|
|
||||||
|
$filter
|
||||||
|
.sink(receiveValue: requestInitItems(_:))
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestNextPage() {
|
func requestNextPage() {
|
||||||
page += 1
|
page += 1
|
||||||
requestItems()
|
requestItems(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestPreviousPage() {
|
func requestPreviousPage() {
|
||||||
page -= 1
|
page -= 1
|
||||||
requestItems()
|
requestItems(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestInitItems() {
|
func requestInitItems(_ filter: Filter) {
|
||||||
page = 1
|
page = 1
|
||||||
requestItems()
|
requestItems(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func requestItems() {
|
fileprivate func requestItems(_ filter: Filter) {
|
||||||
print(globalData.server?.baseURI)
|
print("ASDASDA")
|
||||||
print(globalData.authHeader)
|
print(globalData.authHeader)
|
||||||
print(filter)
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
provider.requestPublisher(.items(globalData: globalData, filter: filter, page: page))
|
provider.requestPublisher(.items(globalData: globalData, filter: filter, page: page))
|
||||||
// .map(ResumeItem.self) TO DO
|
// .map(ResumeItem.self) TO DO
|
||||||
.print()
|
.print()
|
||||||
.sink(receiveCompletion: { _ in
|
.receive(on: DispatchQueue.main)
|
||||||
self.isLoading = false
|
.map { response -> ([ResumeItem], Int) in
|
||||||
}, receiveValue: { response in
|
|
||||||
self.items.removeAll()
|
|
||||||
let body = response.data
|
let body = response.data
|
||||||
var totalCount = 0
|
var totalCount = 0
|
||||||
|
var innerItems = [ResumeItem]()
|
||||||
do {
|
do {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
totalCount = json["TotalRecordCount"].int ?? 0
|
totalCount = json["TotalRecordCount"].int ?? 0
|
||||||
for (_, item): (String, JSON) in json["Items"] {
|
for (_, item): (String, JSON) in json["Items"] {
|
||||||
// Do something you want
|
// Do something you want
|
||||||
let itemObj = ResumeItem()
|
var itemObj = ResumeItem()
|
||||||
itemObj.Type = item["Type"].string ?? ""
|
itemObj.Type = item["Type"].string ?? ""
|
||||||
if itemObj.Type == "Series" {
|
if itemObj.Type == "Series" {
|
||||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||||
|
@ -120,21 +115,29 @@ final class LibraryViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||||
|
|
||||||
self.items.append(itemObj)
|
innerItems.append(itemObj)
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
return (innerItems, totalCount)
|
||||||
if totalCount > 100 {
|
}
|
||||||
|
.sink(receiveCompletion: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.isLoading = false
|
||||||
|
}, receiveValue: { [weak self] items, count in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if count > 100 {
|
||||||
if self.page > 1 {
|
if self.page > 1 {
|
||||||
self.isHiddenPreviousButton = false
|
self.isHiddenPreviousButton = false
|
||||||
}
|
}
|
||||||
if totalCount > (self.page * 100) {
|
if count > (self.page * 100) {
|
||||||
self.isHiddenNextButton = false
|
self.isHiddenNextButton = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.isHiddenNextButton = true
|
self.isHiddenNextButton = true
|
||||||
self.isHiddenPreviousButton = true
|
self.isHiddenPreviousButton = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.items = items
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,16 +52,18 @@ final class LibrarySearchViewModel: ObservableObject {
|
||||||
provider.requestPublisher(.search(globalData: globalData, filter: filter, searchQuery: query, page: page))
|
provider.requestPublisher(.search(globalData: globalData, filter: filter, searchQuery: query, page: page))
|
||||||
// .map(ResumeItem.self) TO DO
|
// .map(ResumeItem.self) TO DO
|
||||||
.print()
|
.print()
|
||||||
.sink(receiveCompletion: { _ in
|
.sink(receiveCompletion: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { [weak self] response in
|
||||||
|
guard let self = self else { return }
|
||||||
let body = response.data
|
let body = response.data
|
||||||
self.items.removeAll()
|
var innerItems = [ResumeItem]()
|
||||||
do {
|
do {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
for (_, item): (String, JSON) in json["Items"] {
|
for (_, item): (String, JSON) in json["Items"] {
|
||||||
// Do something you want
|
// Do something you want
|
||||||
let itemObj = ResumeItem()
|
var itemObj = ResumeItem()
|
||||||
itemObj.Type = item["Type"].string ?? ""
|
itemObj.Type = item["Type"].string ?? ""
|
||||||
if itemObj.Type == "Series" {
|
if itemObj.Type == "Series" {
|
||||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||||
|
@ -93,9 +95,10 @@ final class LibrarySearchViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||||
|
|
||||||
self.items.append(itemObj)
|
innerItems.append(itemObj)
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
self.items = innerItems
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,81 +5,97 @@
|
||||||
// Created by Aiden Vigue on 5/13/21.
|
// Created by Aiden Vigue on 5/13/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftyRequest
|
|
||||||
import SwiftyJSON
|
|
||||||
import SDWebImageSwiftUI
|
import SDWebImageSwiftUI
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
import SwiftyRequest
|
||||||
|
|
||||||
struct EpisodeItemView: View {
|
struct EpisodeItemView: View {
|
||||||
@EnvironmentObject private var globalData: GlobalData
|
@EnvironmentObject
|
||||||
@EnvironmentObject private var orientationInfo: OrientationInfo
|
private var globalData: GlobalData
|
||||||
@EnvironmentObject private var playbackInfo: ItemPlayback
|
@EnvironmentObject
|
||||||
var item: ResumeItem;
|
private var orientationInfo: OrientationInfo
|
||||||
var fullItem: DetailItem;
|
@EnvironmentObject
|
||||||
|
private var playbackInfo: ItemPlayback
|
||||||
|
var item: ResumeItem
|
||||||
|
var fullItem: DetailItem
|
||||||
|
|
||||||
@State private var isLoading: Bool = true;
|
@State
|
||||||
@State private var progressString: String = "";
|
private var isLoading: Bool = true
|
||||||
@State private var viewDidLoad: Bool = false;
|
@State
|
||||||
|
private var progressString: String = ""
|
||||||
|
@State
|
||||||
|
private var viewDidLoad: Bool = false
|
||||||
|
|
||||||
@State private var watched: Bool = false {
|
@State
|
||||||
|
private var watched: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if(watched == true) {
|
if watched == true {
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
|
||||||
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)?DatePlayed=\(formatter.string(from: date).replacingOccurrences(of: ":", with: "%3A"))")
|
let request = RestRequest(method: .post,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)?DatePlayed=\(formatter.string(from: date).replacingOccurrences(of: ":", with: "%3A"))")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let request = RestRequest(method: .delete, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)")
|
let request = RestRequest(method: .delete,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
@State private var favorite: Bool = false {
|
@State
|
||||||
|
private var favorite: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if(favorite == true) {
|
if favorite == true {
|
||||||
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
let request = RestRequest(method: .post,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let request = RestRequest(method: .delete, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
let request = RestRequest(method: .delete,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
init(item: ResumeItem) {
|
init(item: ResumeItem) {
|
||||||
self.item = item;
|
self.item = item
|
||||||
fullItem = DetailItem();
|
self.fullItem = DetailItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadData() {
|
func loadData() {
|
||||||
if(_viewDidLoad.wrappedValue == true) {
|
if _viewDidLoad.wrappedValue == true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_viewDidLoad.wrappedValue = true;
|
_viewDidLoad.wrappedValue = true
|
||||||
let url = "/Users/\(globalData.user?.user_id ?? "")/Items/\(item.Id)"
|
let url = "/Users/\(globalData.user?.user_id ?? "")/Items/\(item.Id)"
|
||||||
|
|
||||||
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
|
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
|
||||||
|
@ -87,9 +103,9 @@ struct EpisodeItemView: View {
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (result: Result<RestResponse<Data>, RestError>) in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let response):
|
case let .success(response):
|
||||||
let body = response.body
|
let body = response.body
|
||||||
do {
|
do {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
|
@ -110,109 +126,121 @@ struct EpisodeItemView: View {
|
||||||
fullItem.SeriesName = json["SeriesName"].string ?? nil
|
fullItem.SeriesName = json["SeriesName"].string ?? nil
|
||||||
fullItem.Progress = Double(json["UserData"]["PlaybackPositionTicks"].int ?? 0)
|
fullItem.Progress = Double(json["UserData"]["PlaybackPositionTicks"].int ?? 0)
|
||||||
fullItem.OfficialRating = json["OfficialRating"].string ?? "PG-13"
|
fullItem.OfficialRating = json["OfficialRating"].string ?? "PG-13"
|
||||||
fullItem.Watched = json["UserData"]["Played"].bool ?? false;
|
fullItem.Watched = json["UserData"]["Played"].bool ?? false
|
||||||
fullItem.CommunityRating = String(json["CommunityRating"].float ?? 0.0);
|
fullItem.CommunityRating = String(json["CommunityRating"].float ?? 0.0)
|
||||||
fullItem.CriticRating = String(json["CriticRating"].int ?? 0);
|
fullItem.CriticRating = String(json["CriticRating"].int ?? 0)
|
||||||
fullItem.ParentId = json["ParentId"].string ?? ""
|
fullItem.ParentId = json["ParentId"].string ?? ""
|
||||||
fullItem.ParentBackdropItemId = json["ParentBackdropItemId"].string ?? ""
|
fullItem.ParentBackdropItemId = json["ParentBackdropItemId"].string ?? ""
|
||||||
//People
|
// People
|
||||||
fullItem.Directors = []
|
fullItem.Directors = []
|
||||||
fullItem.Studios = []
|
fullItem.Studios = []
|
||||||
fullItem.Writers = []
|
fullItem.Writers = []
|
||||||
fullItem.Cast = []
|
fullItem.Cast = []
|
||||||
fullItem.Genres = []
|
fullItem.Genres = []
|
||||||
|
|
||||||
for (_,person):(String, JSON) in json["People"] {
|
for (_, person): (String, JSON) in json["People"] {
|
||||||
if(person["Type"].stringValue == "Director") {
|
if person["Type"].stringValue == "Director" {
|
||||||
fullItem.Directors.append(person["Name"].string ?? "");
|
fullItem.Directors.append(person["Name"].string ?? "")
|
||||||
} else if(person["Type"].stringValue == "Writer") {
|
} else if person["Type"].stringValue == "Writer" {
|
||||||
fullItem.Writers.append(person["Name"].string ?? "");
|
fullItem.Writers.append(person["Name"].string ?? "")
|
||||||
} else if(person["Type"].stringValue == "Actor") {
|
} else if person["Type"].stringValue == "Actor" {
|
||||||
let cast = CastMember();
|
let cast = CastMember()
|
||||||
cast.Name = person["Name"].string ?? "";
|
cast.Name = person["Name"].string ?? ""
|
||||||
cast.Id = person["Id"].string ?? "";
|
cast.Id = person["Id"].string ?? ""
|
||||||
let imageTag = person["PrimaryImageTag"].string ?? "";
|
let imageTag = person["PrimaryImageTag"].string ?? ""
|
||||||
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? "";
|
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""
|
||||||
cast.Role = person["Role"].string ?? "";
|
cast.Role = person["Role"].string ?? ""
|
||||||
cast.Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxHeight=250&quality=85&tag=\(imageTag)")!
|
cast
|
||||||
fullItem.Cast.append(cast);
|
.Image =
|
||||||
|
URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxHeight=250&quality=85&tag=\(imageTag)")!
|
||||||
|
fullItem.Cast.append(cast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Studios
|
// Studios
|
||||||
for (_,studio):(String, JSON) in json["Studios"] {
|
for (_, studio): (String, JSON) in json["Studios"] {
|
||||||
fullItem.Studios.append(studio["Name"].string ?? "");
|
fullItem.Studios.append(studio["Name"].string ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Genres
|
// Genres
|
||||||
for (_,genre):(String, JSON) in json["GenreItems"] {
|
for (_, genre): (String, JSON) in json["GenreItems"] {
|
||||||
let tmpGenre = IVGenre()
|
let tmpGenre = IVGenre()
|
||||||
tmpGenre.Id = genre["Id"].string ?? "";
|
tmpGenre.Id = genre["Id"].string ?? ""
|
||||||
tmpGenre.Name = genre["Name"].string ?? "";
|
tmpGenre.Name = genre["Name"].string ?? ""
|
||||||
fullItem.Genres.append(tmpGenre);
|
fullItem.Genres.append(tmpGenre)
|
||||||
}
|
}
|
||||||
|
|
||||||
_watched.wrappedValue = fullItem.Watched
|
_watched.wrappedValue = fullItem.Watched
|
||||||
_favorite.wrappedValue = json["UserData"]["IsFavorite"].bool ?? false;
|
_favorite.wrappedValue = json["UserData"]["IsFavorite"].bool ?? false
|
||||||
|
|
||||||
//Process runtime
|
// Process runtime
|
||||||
let seconds: Int = ((json["RunTimeTicks"].int ?? 0)/10000000)
|
let seconds: Int = ((json["RunTimeTicks"].int ?? 0) / 10_000_000)
|
||||||
fullItem.RuntimeTicks = json["RunTimeTicks"].int ?? 0;
|
fullItem.RuntimeTicks = json["RunTimeTicks"].int ?? 0
|
||||||
let hours = (seconds/3600)
|
let hours = (seconds / 3600)
|
||||||
let minutes = ((seconds - (hours * 3600))/60)
|
let minutes = ((seconds - (hours * 3600)) / 60)
|
||||||
if(hours != 0) {
|
if hours != 0 {
|
||||||
fullItem.Runtime = "\(hours):\(String(minutes).leftPad(toWidth: 2, withString: "0"))"
|
fullItem.Runtime = "\(hours):\(String(minutes).leftPad(toWidth: 2, withString: "0"))"
|
||||||
} else {
|
} else {
|
||||||
fullItem.Runtime = "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m"
|
fullItem.Runtime = "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fullItem.Progress != 0) {
|
if fullItem.Progress != 0 {
|
||||||
let remainingSecs = (Double(json["RunTimeTicks"].int ?? 0) - fullItem.Progress)/10000000
|
let remainingSecs = (Double(json["RunTimeTicks"].int ?? 0) - fullItem.Progress) / 10_000_000
|
||||||
let proghours = Int(remainingSecs/3600)
|
let proghours = Int(remainingSecs / 3600)
|
||||||
let progminutes = Int((Int(remainingSecs) - (proghours * 3600))/60)
|
let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60)
|
||||||
if(proghours != 0) {
|
if proghours != 0 {
|
||||||
_progressString.wrappedValue = "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
|
_progressString.wrappedValue = "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
|
||||||
} else {
|
} else {
|
||||||
_progressString.wrappedValue = "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
_progressString.wrappedValue = "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {}
|
||||||
|
case let .failure(error):
|
||||||
}
|
|
||||||
break
|
|
||||||
case .failure(let error):
|
|
||||||
debugPrint(error)
|
debugPrint(error)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
_isLoading.wrappedValue = false;
|
_isLoading.wrappedValue = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
LoadingView(isShowing: $isLoading) {
|
LoadingView(isShowing: $isLoading) {
|
||||||
VStack(alignment:.leading) {
|
VStack(alignment: .leading) {
|
||||||
if(!isLoading) {
|
if !isLoading {
|
||||||
if(orientationInfo.orientation == .portrait) {
|
if orientationInfo.orientation == .portrait {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
VStack() {
|
VStack {
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
|
||||||
|
.BackdropBlurHash,
|
||||||
|
size: CGSize(width: 32, height: 32))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets
|
||||||
|
.trailing,
|
||||||
|
height: UIDevice.current
|
||||||
|
.userInterfaceIdiom == .pad ? 350 :
|
||||||
|
(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets
|
||||||
|
.trailing) * 0.5625)
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity(0.3)
|
.opacity(0.3)
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||||
|
height: UIDevice.current
|
||||||
|
.userInterfaceIdiom == .pad ? 350 :
|
||||||
|
(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) *
|
||||||
|
0.5625)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.overlay(
|
.overlay(HStack {
|
||||||
HStack() {
|
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
fullItem.PosterBlurHash,
|
||||||
|
size: CGSize(width: 32, height: 32))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 120, height: 180)
|
.frame(width: 120, height: 180)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
|
@ -226,7 +254,7 @@ struct EpisodeItemView: View {
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.offset(y: -4)
|
.offset(y: -4)
|
||||||
HStack() {
|
HStack {
|
||||||
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -235,19 +263,17 @@ struct EpisodeItemView: View {
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if(fullItem.OfficialRating != "") {
|
if fullItem.OfficialRating != "" {
|
||||||
Text(fullItem.OfficialRating).font(.subheadline)
|
Text(fullItem.OfficialRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||||
.overlay(
|
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||||
RoundedRectangle(cornerRadius: 2)
|
.stroke(Color.secondary, lineWidth: 1))
|
||||||
.stroke(Color.secondary, lineWidth: 1)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if(fullItem.CommunityRating != "0") {
|
if fullItem.CommunityRating != "0" {
|
||||||
HStack() {
|
HStack {
|
||||||
Image(systemName: "star").foregroundColor(.secondary)
|
Image(systemName: "star").foregroundColor(.secondary)
|
||||||
Text(fullItem.CommunityRating).font(.subheadline)
|
Text(fullItem.CommunityRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
@ -257,78 +283,99 @@ struct EpisodeItemView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}.frame(maxWidth: .infinity, alignment: .leading).offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46).padding(.trailing, 16)
|
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40)
|
.offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46)
|
||||||
, alignment: .bottomLeading)
|
.padding(.trailing, 16)
|
||||||
|
}.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40),
|
||||||
|
alignment: .bottomLeading)
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack() {
|
HStack {
|
||||||
//Play button
|
// Play button
|
||||||
Button {
|
Button {
|
||||||
self.playbackInfo.itemToPlay = fullItem;
|
self.playbackInfo.itemToPlay = fullItem
|
||||||
self.playbackInfo.shouldPlay = true;
|
self.playbackInfo.shouldPlay = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack() {
|
HStack {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left")
|
||||||
|
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
||||||
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
}
|
}
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}.buttonStyle(PlainButtonStyle())
|
}.buttonStyle(PlainButtonStyle())
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack() {
|
HStack {
|
||||||
Button() {
|
Button {
|
||||||
favorite.toggle()
|
favorite.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(!favorite) {
|
if !favorite {
|
||||||
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20))
|
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed))
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button() {
|
Button {
|
||||||
watched.toggle()
|
watched.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(watched) {
|
if watched {
|
||||||
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
ScrollView() {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if(fullItem.Tagline != "") {
|
if fullItem.Tagline != "" {
|
||||||
Text(fullItem.Tagline).font(.body).italic().padding(.top, 7).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,16)
|
Text(fullItem.Tagline).font(.body).italic().padding(.top, 7)
|
||||||
|
.fixedSize(horizontal: false, vertical: true).padding(.leading, 16)
|
||||||
|
.padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,16)
|
Text(fullItem.Overview).font(.footnote).padding(.top, 3)
|
||||||
if(fullItem.Genres.count != 0) {
|
.fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16)
|
||||||
|
.padding(.trailing, 16)
|
||||||
|
if !fullItem.Genres.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
ForEach(fullItem.Genres, id: \.Id) { genre in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(genres: [
|
||||||
|
genre
|
||||||
|
.Name,
|
||||||
|
])), title: genre.Name)) {
|
||||||
Text(genre.Name).font(.footnote)
|
Text(genre.Name).font(.footnote)
|
||||||
// }
|
|
||||||
}
|
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fullItem.Cast.count != 0) {
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fullItem.Cast.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
VStack() {
|
VStack {
|
||||||
Spacer().frame(height: 8);
|
Spacer().frame(height: 8)
|
||||||
HStack() {
|
HStack {
|
||||||
Spacer().frame(width: 16)
|
Spacer().frame(width: 16)
|
||||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(personIds: [
|
||||||
VStack() {
|
cast
|
||||||
|
.Id,
|
||||||
|
])), title: cast.Name)) {
|
||||||
|
VStack {
|
||||||
WebImage(url: cast.Image)
|
WebImage(url: cast.Image)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!)
|
Image(uiImage: UIImage(blurHash: cast
|
||||||
|
.ImageBlurHash == "" ?
|
||||||
|
"W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
cast.ImageBlurHash,
|
||||||
|
size: CGSize(width: 16,
|
||||||
|
height: 16))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
|
@ -337,12 +384,14 @@ struct EpisodeItemView: View {
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1)
|
||||||
if(cast.Role != "") {
|
.frame(width: 100).foregroundColor(Color.primary)
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
if cast.Role != "" {
|
||||||
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1)
|
||||||
|
.foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
Spacer().frame(width: 10)
|
Spacer().frame(width: 10)
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 16)
|
Spacer().frame(width: 16)
|
||||||
|
@ -350,51 +399,66 @@ struct EpisodeItemView: View {
|
||||||
}
|
}
|
||||||
}.padding(.top, -3)
|
}.padding(.top, -3)
|
||||||
}
|
}
|
||||||
if(fullItem.Directors.count != 0) {
|
if !fullItem.Directors.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Directors:").font(.callout).fontWeight(.semibold)
|
Text("Directors:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
if(fullItem.Writers.count != 0) {
|
if !fullItem.Writers.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Writers:").font(.callout).fontWeight(.semibold)
|
Text("Writers:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
if(fullItem.Studios.count != 0) {
|
if !fullItem.Studios.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 3)
|
Spacer().frame(height: 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0, trailing: 0))
|
}
|
||||||
|
.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0,
|
||||||
|
trailing: 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
ZStack() {
|
ZStack {
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
|
||||||
|
.BackdropBlurHash,
|
||||||
|
size: CGSize(width: 32, height: 32))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets
|
||||||
|
.trailing,
|
||||||
|
height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets
|
||||||
|
.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity(0.3)
|
.opacity(0.3)
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||||
|
height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
HStack() {
|
HStack {
|
||||||
VStack() {
|
VStack {
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
fullItem.PosterBlurHash,
|
||||||
|
size: CGSize(width: 32, height: 32))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 120, height: 180)
|
.frame(width: 120, height: 180)
|
||||||
}
|
}
|
||||||
|
@ -404,23 +468,24 @@ struct EpisodeItemView: View {
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
Spacer().frame(height: 15)
|
Spacer().frame(height: 15)
|
||||||
Button {
|
Button {
|
||||||
self.playbackInfo.itemToPlay = fullItem;
|
self.playbackInfo.itemToPlay = fullItem
|
||||||
self.playbackInfo.shouldPlay = true;
|
self.playbackInfo.shouldPlay = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack() {
|
HStack {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left")
|
||||||
|
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
||||||
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
}
|
}
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}.buttonStyle(PlainButtonStyle())
|
}.buttonStyle(PlainButtonStyle())
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
ScrollView() {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack() {
|
HStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(fullItem.Name).font(.headline)
|
Text(fullItem.Name).font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
@ -428,7 +493,7 @@ struct EpisodeItemView: View {
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.offset(x: 14, y: 0)
|
.offset(x: 14, y: 0)
|
||||||
Spacer().frame(height: 1)
|
Spacer().frame(height: 1)
|
||||||
HStack() {
|
HStack {
|
||||||
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -437,19 +502,17 @@ struct EpisodeItemView: View {
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if(fullItem.OfficialRating != "") {
|
if fullItem.OfficialRating != "" {
|
||||||
Text(fullItem.OfficialRating).font(.subheadline)
|
Text(fullItem.OfficialRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||||
.overlay(
|
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||||
RoundedRectangle(cornerRadius: 2)
|
.stroke(Color.secondary, lineWidth: 1))
|
||||||
.stroke(Color.secondary, lineWidth: 1)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if(fullItem.CommunityRating != "0") {
|
if fullItem.CommunityRating != "0" {
|
||||||
HStack() {
|
HStack {
|
||||||
Image(systemName: "star").foregroundColor(.secondary)
|
Image(systemName: "star").foregroundColor(.secondary)
|
||||||
Text(fullItem.CommunityRating).font(.subheadline)
|
Text(fullItem.CommunityRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
@ -463,56 +526,76 @@ struct EpisodeItemView: View {
|
||||||
.offset(x: 14)
|
.offset(x: 14)
|
||||||
}.frame(maxWidth: .infinity)
|
}.frame(maxWidth: .infinity)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack() {
|
HStack {
|
||||||
Button() {
|
Button {
|
||||||
favorite.toggle()
|
favorite.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(!favorite) {
|
if !favorite {
|
||||||
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "heart").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20))
|
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed))
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button() {
|
Button {
|
||||||
watched.toggle()
|
watched.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(watched) {
|
if watched {
|
||||||
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
}.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
if(fullItem.Tagline != "") {
|
if fullItem.Tagline != "" {
|
||||||
Text(fullItem.Tagline).font(.body).italic().padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
Text(fullItem.Tagline).font(.body).italic().padding(.top, 3)
|
||||||
|
.fixedSize(horizontal: false, vertical: true).padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
Text(fullItem.Overview).font(.footnote).padding(.top, 3)
|
||||||
if(fullItem.Genres.count != 0) {
|
.fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
if !fullItem.Genres.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
ForEach(fullItem.Genres, id: \.Id) { genre in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(genres: [
|
||||||
|
genre
|
||||||
|
.Name,
|
||||||
|
])), title: genre.Name)) {
|
||||||
Text(genre.Name).font(.footnote)
|
Text(genre.Name).font(.footnote)
|
||||||
// }
|
|
||||||
}
|
|
||||||
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fullItem.Cast.count != 0) {
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fullItem.Cast.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
VStack() {
|
VStack {
|
||||||
Spacer().frame(height: 8);
|
Spacer().frame(height: 8)
|
||||||
HStack() {
|
HStack {
|
||||||
Spacer().frame(width: 16)
|
Spacer().frame(width: 16)
|
||||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(personIds: [
|
||||||
VStack() {
|
cast
|
||||||
|
.Id,
|
||||||
|
])), title: cast.Name)) {
|
||||||
|
VStack {
|
||||||
WebImage(url: cast.Image)
|
WebImage(url: cast.Image)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!)
|
Image(uiImage: UIImage(blurHash: cast
|
||||||
|
.ImageBlurHash == "" ?
|
||||||
|
"W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
cast.ImageBlurHash,
|
||||||
|
size: CGSize(width: 16,
|
||||||
|
height: 16))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
|
@ -521,12 +604,14 @@ struct EpisodeItemView: View {
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1)
|
||||||
if(cast.Role != "") {
|
.frame(width: 100).foregroundColor(Color.primary)
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
if cast.Role != "" {
|
||||||
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1)
|
||||||
|
.foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
Spacer().frame(width: 10)
|
Spacer().frame(width: 10)
|
||||||
}
|
}
|
||||||
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
@ -534,28 +619,35 @@ struct EpisodeItemView: View {
|
||||||
}
|
}
|
||||||
}.padding(.top, -3)
|
}.padding(.top, -3)
|
||||||
}
|
}
|
||||||
if(fullItem.Directors.count != 0) {
|
if !fullItem.Directors.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Directors:").font(.callout).fontWeight(.semibold)
|
Text("Directors:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
if(fullItem.Writers.count != 0) {
|
if !fullItem.Writers.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Writers:").font(.callout).fontWeight(.semibold)
|
Text("Writers:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
if(fullItem.Studios.count != 0) {
|
if !fullItem.Studios.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 100);
|
Spacer().frame(height: 100)
|
||||||
}.frame(maxHeight: .infinity)
|
}.frame(maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
}.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading)
|
}.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
.edgesIgnoringSafeArea(.leading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,25 +53,25 @@ struct ServerAuthByNameResponse: Codable {
|
||||||
var AccessToken: String
|
var AccessToken: String
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResumeItem: ObservableObject {
|
struct ResumeItem {
|
||||||
@Published var Name: String = "";
|
var Name: String = "";
|
||||||
@Published var Id: String = "";
|
var Id: String = "";
|
||||||
@Published var IndexNumber: Int? = nil;
|
var IndexNumber: Int? = nil;
|
||||||
@Published var ParentIndexNumber: Int? = nil;
|
var ParentIndexNumber: Int? = nil;
|
||||||
@Published var Image: String = "";
|
var Image: String = "";
|
||||||
@Published var ImageType: String = "";
|
var ImageType: String = "";
|
||||||
@Published var BlurHash: String = "";
|
var BlurHash: String = "";
|
||||||
@Published var `Type`: String = "";
|
var `Type`: String = "";
|
||||||
@Published var SeasonId: String? = nil;
|
var SeasonId: String? = nil;
|
||||||
@Published var SeriesId: String? = nil;
|
var SeriesId: String? = nil;
|
||||||
@Published var SeriesName: String? = nil;
|
var SeriesName: String? = nil;
|
||||||
@Published var ItemProgress: Double = 0;
|
var ItemProgress: Double = 0;
|
||||||
@Published var SeasonImage: String? = nil;
|
var SeasonImage: String? = nil;
|
||||||
@Published var SeasonImageType: String? = nil;
|
var SeasonImageType: String? = nil;
|
||||||
@Published var SeasonImageBlurHash: String? = nil;
|
var SeasonImageBlurHash: String? = nil;
|
||||||
@Published var ItemBadge: Int? = 0;
|
var ItemBadge: Int? = 0;
|
||||||
@Published var ProductionYear: Int = 1999;
|
var ProductionYear: Int = 1999;
|
||||||
@Published var Watched: Bool = false;
|
var Watched: Bool = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServerMeResponse: Codable {
|
struct ServerMeResponse: Codable {
|
||||||
|
|
|
@ -47,7 +47,7 @@ struct LatestMediaView: View {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
for (_,item):(String, JSON) in json {
|
for (_,item):(String, JSON) in json {
|
||||||
// Do something you want
|
// Do something you want
|
||||||
let itemObj = ResumeItem()
|
var itemObj = ResumeItem()
|
||||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||||
itemObj.ImageType = "Primary"
|
itemObj.ImageType = "Primary"
|
||||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||||
|
|
|
@ -14,51 +14,56 @@ struct Genre: Hashable, Identifiable {
|
||||||
var id: String { name }
|
var id: String { name }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct LibraryFilterView: View {
|
struct LibraryFilterView: View {
|
||||||
@Environment(\.managedObjectContext) private var viewContext
|
@Environment(\.presentationMode)
|
||||||
@EnvironmentObject var globalData: GlobalData
|
var presentationMode
|
||||||
|
@Environment(\.managedObjectContext)
|
||||||
|
private var viewContext
|
||||||
|
@EnvironmentObject
|
||||||
|
var globalData: GlobalData
|
||||||
|
|
||||||
@State var library: String;
|
@State
|
||||||
@Binding var output: String;
|
var library: String
|
||||||
@State private var isLoading: Bool = true;
|
|
||||||
@State private var onlyUnplayed: Bool = false;
|
|
||||||
@State private var allGenres: [Genre] = [];
|
|
||||||
@State private var selectedGenres: Set<Genre> = [];
|
|
||||||
|
|
||||||
@State private var allRatings: [Genre] = [];
|
@Binding
|
||||||
@State private var selectedRatings: Set<Genre> = [];
|
var filter: Filter
|
||||||
@State private var sortBySelection: String = "SortName";
|
@State
|
||||||
@State private var sortOrder: String = "Descending";
|
private var isLoading: Bool = true
|
||||||
@State private var viewDidLoad: Bool = false;
|
@State
|
||||||
@Binding var close: Bool;
|
private var onlyUnplayed: Bool = false
|
||||||
|
@State
|
||||||
|
private var allGenres: [Genre] = []
|
||||||
|
@State
|
||||||
|
private var selectedGenres: Set<Genre> = []
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var allRatings: [Genre] = []
|
||||||
|
@State
|
||||||
|
private var selectedRatings: Set<Genre> = []
|
||||||
|
@State
|
||||||
|
private var sortBySelection: String = "SortName"
|
||||||
|
@State
|
||||||
|
private var sortOrder: String = "Descending"
|
||||||
|
@State
|
||||||
|
private var viewDidLoad: Bool = false
|
||||||
|
|
||||||
func onAppear() {
|
func onAppear() {
|
||||||
if(_viewDidLoad.wrappedValue == true) {
|
if _viewDidLoad.wrappedValue == true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_viewDidLoad.wrappedValue = true;
|
_viewDidLoad.wrappedValue = true
|
||||||
if(_output.wrappedValue.contains("&Filters=IsUnplayed")) {
|
if filter.filterTypes.contains(.isUnplayed) {
|
||||||
_onlyUnplayed.wrappedValue = true;
|
_onlyUnplayed.wrappedValue = true
|
||||||
}
|
}
|
||||||
if(_output.wrappedValue.contains("&Genres=")) {
|
if !filter.genres.isEmpty {
|
||||||
let genreString = _output.wrappedValue.components(separatedBy: "&Genres=")[1].components(separatedBy: "&")[0];
|
_selectedGenres.wrappedValue = Set(filter.genres.map { Genre(name: $0) })
|
||||||
for genre in genreString.components(separatedBy: "%7C") {
|
|
||||||
_selectedGenres.wrappedValue.insert(Genre(name: genre.removingPercentEncoding ?? ""))
|
|
||||||
}
|
}
|
||||||
|
if !filter.officialRatings.isEmpty {
|
||||||
|
_selectedRatings.wrappedValue = Set(filter.officialRatings.map { Genre(name: $0) })
|
||||||
}
|
}
|
||||||
if(_output.wrappedValue.contains("&OfficialRatings=")) {
|
_sortBySelection.wrappedValue = filter.sort?.rawValue ?? sortBySelection
|
||||||
let ratingString = _output.wrappedValue.components(separatedBy: "&OfficialRatings=")[1].components(separatedBy: "&")[0];
|
_sortOrder.wrappedValue = filter.asc?.rawValue ?? sortOrder
|
||||||
for rating in ratingString.components(separatedBy: "%7C") {
|
|
||||||
_selectedRatings.wrappedValue.insert(Genre(name: rating.removingPercentEncoding ?? ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let sortBy = _output.wrappedValue.components(separatedBy: "&SortBy=")[1].components(separatedBy: "&")[0];
|
|
||||||
_sortBySelection.wrappedValue = sortBy;
|
|
||||||
let sortOrder = _output.wrappedValue.components(separatedBy: "&SortOrder=")[1].components(separatedBy: "&")[0];
|
|
||||||
_sortOrder.wrappedValue = sortOrder;
|
|
||||||
|
|
||||||
recalculateFilters()
|
|
||||||
_allGenres.wrappedValue = []
|
_allGenres.wrappedValue = []
|
||||||
let url = "/Items/Filters?UserId=\(globalData.user?.user_id ?? "")&ParentId=\(library)"
|
let url = "/Items/Filters?UserId=\(globalData.user?.user_id ?? "")&ParentId=\(library)"
|
||||||
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
|
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
|
||||||
|
@ -66,88 +71,58 @@ struct LibraryFilterView: View {
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (result: Result<RestResponse<Data>, RestError>) in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let response):
|
case let .success(response):
|
||||||
let body = response.body
|
let body = response.body
|
||||||
do {
|
do {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
let arr = json["Genres"].arrayObject as? [String] ?? []
|
let arr = json["Genres"].arrayObject as? [String] ?? []
|
||||||
for genreName in arr {
|
for genreName in arr {
|
||||||
//print(genreName)
|
// print(genreName)
|
||||||
let genre = Genre(name: genreName)
|
let genre = Genre(name: genreName)
|
||||||
allGenres.append(genre)
|
allGenres.append(genre)
|
||||||
}
|
}
|
||||||
|
|
||||||
let arr2 = json["OfficialRatings"].arrayObject as? [String] ?? []
|
let arr2 = json["OfficialRatings"].arrayObject as? [String] ?? []
|
||||||
for genreName in arr2 {
|
for genreName in arr2 {
|
||||||
//print(genreName)
|
// print(genreName)
|
||||||
let genre = Genre(name: genreName)
|
let genre = Genre(name: genreName)
|
||||||
allRatings.append(genre)
|
allRatings.append(genre)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {}
|
||||||
|
case let .failure(error):
|
||||||
}
|
|
||||||
break
|
|
||||||
case .failure(let error):
|
|
||||||
debugPrint(error)
|
debugPrint(error)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
isLoading = false;
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func recalculateFilters() {
|
|
||||||
print("recalcFilters running");
|
|
||||||
output = "";
|
|
||||||
if(_onlyUnplayed.wrappedValue) {
|
|
||||||
output = "&Filters=IsUnPlayed";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selectedGenres.count != 0) {
|
|
||||||
output += "&Genres="
|
|
||||||
var genres: [String] = []
|
|
||||||
for genre in selectedGenres {
|
|
||||||
genres.append(genre.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
|
|
||||||
}
|
|
||||||
output += genres.joined(separator: "%7C")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selectedRatings.count != 0) {
|
|
||||||
output += "&OfficialRatings="
|
|
||||||
var genres: [String] = []
|
|
||||||
for genre in selectedRatings {
|
|
||||||
genres.append(genre.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
|
|
||||||
}
|
|
||||||
output += genres.joined(separator: "%7C")
|
|
||||||
}
|
|
||||||
output += "&SortBy=\(sortBySelection)&SortOrder=\(sortOrder)"
|
|
||||||
//print(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView() {
|
NavigationView {
|
||||||
LoadingView(isShowing: $isLoading) {
|
LoadingView(isShowing: $isLoading) {
|
||||||
Form {
|
Form {
|
||||||
Toggle("Only show unplayed items", isOn: $onlyUnplayed)
|
Toggle("Only show unplayed items", isOn: $onlyUnplayed)
|
||||||
.onChange(of: onlyUnplayed) { tag in
|
.onChange(of: onlyUnplayed) { value in
|
||||||
recalculateFilters()
|
if value {
|
||||||
|
filter.filterTypes.append(.isUnplayed)
|
||||||
|
} else {
|
||||||
|
filter.filterTypes.removeAll { $0 == .isUnplayed }
|
||||||
}
|
}
|
||||||
MultiSelector(
|
}
|
||||||
label: "Genres",
|
MultiSelector(label: "Genres",
|
||||||
options: allGenres,
|
options: allGenres,
|
||||||
optionToString: { $0.name },
|
optionToString: { $0.name },
|
||||||
selected: $selectedGenres
|
selected: $selectedGenres)
|
||||||
).onChange(of: selectedGenres) { tag in
|
.onChange(of: selectedGenres) { genres in
|
||||||
recalculateFilters()
|
filter.genres = genres.map(\.id)
|
||||||
}
|
}
|
||||||
MultiSelector(
|
MultiSelector(label: "Parental Ratings",
|
||||||
label: "Parental Ratings",
|
|
||||||
options: allRatings,
|
options: allRatings,
|
||||||
optionToString: { $0.name },
|
optionToString: { $0.name },
|
||||||
selected: $selectedRatings
|
selected: $selectedRatings)
|
||||||
).onChange(of: selectedRatings) { tag in
|
.onChange(of: selectedRatings) { ratings in
|
||||||
recalculateFilters()
|
filter.officialRatings = ratings.map(\.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Sort settings")) {
|
Section(header: Text("Sort settings")) {
|
||||||
|
@ -157,30 +132,28 @@ struct LibraryFilterView: View {
|
||||||
Text("Date Played").tag("DatePlayed")
|
Text("Date Played").tag("DatePlayed")
|
||||||
Text("Date Released").tag("PremiereDate")
|
Text("Date Released").tag("PremiereDate")
|
||||||
Text("Runtime").tag("Runtime")
|
Text("Runtime").tag("Runtime")
|
||||||
}.onChange(of: sortBySelection) { tag in
|
}.onChange(of: sortBySelection) { value in
|
||||||
recalculateFilters()
|
guard let sort = SortType(rawValue: value) else { return }
|
||||||
|
filter.sort = sort
|
||||||
}
|
}
|
||||||
Picker("Sort order", selection: $sortOrder) {
|
Picker("Sort order", selection: $sortOrder) {
|
||||||
Text("Ascending").tag("Ascending")
|
Text("Ascending").tag("Ascending")
|
||||||
Text("Descending").tag("Descending")
|
Text("Descending").tag("Descending")
|
||||||
}.onChange(of: sortOrder) { tag in
|
}.onChange(of: sortOrder) { order in
|
||||||
recalculateFilters()
|
guard let asc = ASC(rawValue: order) else { return }
|
||||||
|
filter.asc = asc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onAppear(perform: onAppear)
|
}.onAppear(perform: onAppear)
|
||||||
.navigationBarTitle("Filters", displayMode: .inline)
|
.navigationBarTitle("Filters", displayMode: .inline)
|
||||||
.toolbar {
|
.navigationBarItems(leading: Button {
|
||||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
presentationMode.wrappedValue.dismiss()
|
||||||
Button {
|
|
||||||
close = false
|
|
||||||
} label: {
|
} label: {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Back").font(.callout)
|
Text("Back").font(.callout)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,26 +40,25 @@ struct LibraryListView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(libraryIDs, id: \.self) { id in
|
List(libraryIDs, id: \.self) { id in
|
||||||
if id != "genres" {
|
switch id {
|
||||||
NavigationLink(destination: LibraryView(viewModel: .init(prefillID: id), title: libraryNames[id] ?? "")) {
|
case "favorites":
|
||||||
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(filterTypes: [.isFavorite])),
|
||||||
|
title: libraryNames[id] ?? "")) {
|
||||||
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||||
}
|
}
|
||||||
} else {
|
case "genres":
|
||||||
// NavigationLink(destination: LibraryView(prefill: id, names: libraryNames, libraries: library_ids)) {
|
|
||||||
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||||
// }
|
default:
|
||||||
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(parentID: id)), title: libraryNames[id] ?? "")) {
|
||||||
|
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear(perform: listOnAppear)
|
.onAppear(perform: listOnAppear)
|
||||||
.navigationTitle("All Media")
|
.navigationTitle("All Media")
|
||||||
.toolbar {
|
.navigationBarItems(trailing:
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: .init()))) {
|
||||||
// NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: .init()),
|
|
||||||
// close: $closeSearch),
|
|
||||||
// isActive: $closeSearch) {
|
|
||||||
Image(systemName: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
// }
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,31 +15,22 @@ struct LibrarySearchView: View {
|
||||||
private var viewContext
|
private var viewContext
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var globalData: GlobalData
|
var globalData: GlobalData
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: LibrarySearchViewModel
|
var viewModel: LibrarySearchViewModel
|
||||||
|
|
||||||
@Binding
|
|
||||||
var close: Bool
|
|
||||||
@State
|
@State
|
||||||
var open: Bool = false
|
private var tracks: [GridItem] = []
|
||||||
@State
|
|
||||||
private var onlyUnplayed: Bool = false
|
|
||||||
@State
|
|
||||||
private var viewDidLoad: Bool = false
|
|
||||||
@State
|
|
||||||
var linkedItem = ResumeItem()
|
|
||||||
|
|
||||||
func onAppear() {
|
|
||||||
recalcTracks()
|
|
||||||
viewModel.globalData = globalData
|
|
||||||
}
|
|
||||||
|
|
||||||
@Environment(\.verticalSizeClass)
|
@Environment(\.verticalSizeClass)
|
||||||
var verticalSizeClass: UserInterfaceSizeClass?
|
var verticalSizeClass: UserInterfaceSizeClass?
|
||||||
@Environment(\.horizontalSizeClass)
|
@Environment(\.horizontalSizeClass)
|
||||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||||
|
|
||||||
|
func onAppear() {
|
||||||
|
recalcTracks()
|
||||||
|
viewModel.globalData = globalData
|
||||||
|
}
|
||||||
|
|
||||||
var isPortrait: Bool {
|
var isPortrait: Bool {
|
||||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||||
return result
|
return result
|
||||||
|
@ -53,15 +44,9 @@ struct LibrarySearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@State
|
|
||||||
private var tracks: [GridItem] = []
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
VStack {
|
VStack {
|
||||||
NavigationLink(destination: ItemView(item: linkedItem), isActive: $open) {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
Spacer().frame(height: 6)
|
Spacer().frame(height: 6)
|
||||||
TextField("Search", text: $viewModel.searchQuery, onEditingChanged: { _ in
|
TextField("Search", text: $viewModel.searchQuery, onEditingChanged: { _ in
|
||||||
print("changed")
|
print("changed")
|
||||||
|
@ -72,11 +57,7 @@ struct LibrarySearchView: View {
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
LazyVGrid(columns: tracks) {
|
LazyVGrid(columns: tracks) {
|
||||||
ForEach(viewModel.items, id: \.Id) { item in
|
ForEach(viewModel.items, id: \.Id) { item in
|
||||||
Button {
|
NavigationLink(destination: ItemView(item: item)) {
|
||||||
_linkedItem.wrappedValue = item
|
|
||||||
_close.wrappedValue = false
|
|
||||||
_open.wrappedValue = true
|
|
||||||
} label: {
|
|
||||||
ResumeItemGridCell(item: item)
|
ResumeItemGridCell(item: item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +68,8 @@ struct LibrarySearchView: View {
|
||||||
}
|
}
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
ActivityIndicator($viewModel.isLoading)
|
ActivityIndicator($viewModel.isLoading)
|
||||||
|
} else if viewModel.items.isEmpty {
|
||||||
|
Text("Empty Response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear(perform: onAppear)
|
.onAppear(perform: onAppear)
|
||||||
|
|
|
@ -18,28 +18,24 @@ struct LibraryView: View {
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: LibraryViewModel
|
var viewModel: LibraryViewModel
|
||||||
|
|
||||||
@State
|
|
||||||
private var viewDidLoad: Bool = false
|
|
||||||
@State
|
@State
|
||||||
private var showFiltersPopover: Bool = false
|
private var showFiltersPopover: Bool = false
|
||||||
@State
|
@State
|
||||||
private var showSearchPopover: Bool = false
|
private var showSearchPopover: Bool = false
|
||||||
|
|
||||||
|
private var title: String
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var title: String = ""
|
private var tracks: [GridItem] = []
|
||||||
@State
|
|
||||||
private var closeSearch: Bool = false
|
|
||||||
|
|
||||||
init(viewModel: LibraryViewModel, title: String) {
|
init(viewModel: LibraryViewModel, title: String) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
self._title = State(initialValue: title)
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
func onAppear() {
|
func onAppear() {
|
||||||
viewModel.globalData = globalData
|
|
||||||
if viewModel.items.isEmpty {
|
|
||||||
recalcTracks()
|
recalcTracks()
|
||||||
viewModel.requestInitItems()
|
viewModel.globalData = globalData
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Environment(\.verticalSizeClass)
|
@Environment(\.verticalSizeClass)
|
||||||
|
@ -60,11 +56,8 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@State
|
|
||||||
private var tracks: [GridItem] = []
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
LoadingView(isShowing: $viewModel.isLoading) {
|
ZStack {
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
Spacer().frame(height: 16)
|
Spacer().frame(height: 16)
|
||||||
LazyVGrid(columns: tracks) {
|
LazyVGrid(columns: tracks) {
|
||||||
|
@ -76,22 +69,19 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 16)
|
Spacer().frame(height: 16)
|
||||||
}
|
}
|
||||||
.gesture(DragGesture().onChanged { value in
|
|
||||||
if value.translation.height > 0 {
|
|
||||||
print("Scroll down")
|
|
||||||
} else {
|
|
||||||
print("Scroll up")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onChange(of: isPortrait) { _ in
|
.onChange(of: isPortrait) { _ in
|
||||||
recalcTracks()
|
recalcTracks()
|
||||||
}
|
}
|
||||||
|
if viewModel.isLoading {
|
||||||
|
ActivityIndicator($viewModel.isLoading)
|
||||||
|
} else if viewModel.items.isEmpty {
|
||||||
|
Text("Empty Response")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.overrideViewPreference(.unspecified)
|
.overrideViewPreference(.unspecified)
|
||||||
.onAppear(perform: onAppear)
|
.onAppear(perform: onAppear)
|
||||||
.navigationTitle(title)
|
.navigationTitle(title)
|
||||||
.toolbar {
|
.navigationBarItems(trailing: HStack {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
|
||||||
if !viewModel.isHiddenPreviousButton {
|
if !viewModel.isHiddenPreviousButton {
|
||||||
Button {
|
Button {
|
||||||
viewModel.requestPreviousPage()
|
viewModel.requestPreviousPage()
|
||||||
|
@ -106,8 +96,7 @@ struct LibraryView: View {
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: viewModel.filter), close: $closeSearch),
|
NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: viewModel.filter))) {
|
||||||
isActive: $closeSearch) {
|
|
||||||
Image(systemName: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
@ -115,13 +104,12 @@ struct LibraryView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "line.horizontal.3.decrease")
|
Image(systemName: "line.horizontal.3.decrease")
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.sheet(isPresented: self.$showFiltersPopover) {
|
||||||
|
LibraryFilterView(library: viewModel.filter.parentID ?? "", filter: $viewModel.filter)
|
||||||
|
.environmentObject(self.globalData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .sheet(isPresented: self.$showFiltersPopover) {
|
|
||||||
// LibraryFilterView(library: selected_library_id, output: $filterString, close: $showFiltersPopover)
|
|
||||||
// .environmentObject(self.globalData)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LibraryView {
|
extension LibraryView {
|
||||||
|
|
|
@ -5,130 +5,188 @@
|
||||||
// Created by Aiden Vigue on 5/13/21.
|
// Created by Aiden Vigue on 5/13/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftyRequest
|
|
||||||
import SwiftyJSON
|
|
||||||
import SDWebImageSwiftUI
|
import SDWebImageSwiftUI
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
import SwiftyRequest
|
||||||
|
|
||||||
class DetailItem: ObservableObject {
|
class DetailItem: ObservableObject {
|
||||||
@Published var Name: String = "";
|
@Published
|
||||||
@Published var Id: String = "";
|
var Name: String = ""
|
||||||
@Published var IndexNumber: Int? = nil;
|
@Published
|
||||||
@Published var ParentIndexNumber: Int? = nil;
|
var Id: String = ""
|
||||||
@Published var Poster: String = "";
|
@Published
|
||||||
@Published var Backdrop: String = ""
|
var IndexNumber: Int? = nil
|
||||||
@Published var PosterBlurHash: String = "";
|
@Published
|
||||||
@Published var BackdropBlurHash: String = "";
|
var ParentIndexNumber: Int? = nil
|
||||||
@Published var `Type`: String = "";
|
@Published
|
||||||
@Published var SeasonId: String? = nil;
|
var Poster: String = ""
|
||||||
@Published var SeriesId: String? = nil;
|
@Published
|
||||||
@Published var SeriesName: String? = nil;
|
var Backdrop: String = ""
|
||||||
@Published var ItemProgress: Double = 0;
|
@Published
|
||||||
@Published var ItemBadge: Int? = 0;
|
var PosterBlurHash: String = ""
|
||||||
@Published var ProductionYear: Int = 1999;
|
@Published
|
||||||
@Published var Runtime: String = "";
|
var BackdropBlurHash: String = ""
|
||||||
@Published var RuntimeTicks: Int = 0;
|
@Published
|
||||||
@Published var Cast: [CastMember] = [];
|
var `Type`: String = ""
|
||||||
@Published var OfficialRating: String = "";
|
@Published
|
||||||
@Published var Progress: Double = 0;
|
var SeasonId: String? = nil
|
||||||
@Published var Watched: Bool = false;
|
@Published
|
||||||
@Published var Overview: String = "";
|
var SeriesId: String? = nil
|
||||||
@Published var Tagline: String = "";
|
@Published
|
||||||
@Published var Directors: [String] = [];
|
var SeriesName: String? = nil
|
||||||
@Published var Writers: [String] = [];
|
@Published
|
||||||
@Published var CriticRating: String = "";
|
var ItemProgress: Double = 0
|
||||||
@Published var CommunityRating: String = "";
|
@Published
|
||||||
@Published var Studios: [String] = [];
|
var ItemBadge: Int? = 0
|
||||||
@Published var ParentId: String = "";
|
@Published
|
||||||
@Published var Genres: [IVGenre] = [];
|
var ProductionYear: Int = 1999
|
||||||
@Published var ProgressStr: String = "";
|
@Published
|
||||||
@Published var ResumeItem: ResumeItem? = nil;
|
var Runtime: String = ""
|
||||||
@Published var ParentBackdropItemId: String = "";
|
@Published
|
||||||
|
var RuntimeTicks: Int = 0
|
||||||
|
@Published
|
||||||
|
var Cast: [CastMember] = []
|
||||||
|
@Published
|
||||||
|
var OfficialRating: String = ""
|
||||||
|
@Published
|
||||||
|
var Progress: Double = 0
|
||||||
|
@Published
|
||||||
|
var Watched: Bool = false
|
||||||
|
@Published
|
||||||
|
var Overview: String = ""
|
||||||
|
@Published
|
||||||
|
var Tagline: String = ""
|
||||||
|
@Published
|
||||||
|
var Directors: [String] = []
|
||||||
|
@Published
|
||||||
|
var Writers: [String] = []
|
||||||
|
@Published
|
||||||
|
var CriticRating: String = ""
|
||||||
|
@Published
|
||||||
|
var CommunityRating: String = ""
|
||||||
|
@Published
|
||||||
|
var Studios: [String] = []
|
||||||
|
@Published
|
||||||
|
var ParentId: String = ""
|
||||||
|
@Published
|
||||||
|
var Genres: [IVGenre] = []
|
||||||
|
@Published
|
||||||
|
var ProgressStr: String = ""
|
||||||
|
@Published
|
||||||
|
var ResumeItem: ResumeItem? = nil
|
||||||
|
@Published
|
||||||
|
var ParentBackdropItemId: String = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
class IVGenre: ObservableObject {
|
class IVGenre: ObservableObject {
|
||||||
@Published var Id: String = "";
|
@Published
|
||||||
@Published var Name: String = "";
|
var Id: String = ""
|
||||||
|
@Published
|
||||||
|
var Name: String = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
class CastMember: ObservableObject {
|
class CastMember: ObservableObject {
|
||||||
@Published var Name: String = "";
|
@Published
|
||||||
@Published var Role: String = "";
|
var Name: String = ""
|
||||||
@Published var ImageBlurHash: String = "";
|
@Published
|
||||||
@Published var Id: String = "";
|
var Role: String = ""
|
||||||
@Published var Image: URL = URL(string: "https://example.com")!;
|
@Published
|
||||||
|
var ImageBlurHash: String = ""
|
||||||
|
@Published
|
||||||
|
var Id: String = ""
|
||||||
|
@Published
|
||||||
|
var Image = URL(string: "https://example.com")!
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MovieItemView: View {
|
struct MovieItemView: View {
|
||||||
@EnvironmentObject private var globalData: GlobalData
|
@EnvironmentObject
|
||||||
@EnvironmentObject private var orientationInfo: OrientationInfo
|
private var globalData: GlobalData
|
||||||
@EnvironmentObject private var playbackInfo: ItemPlayback
|
@EnvironmentObject
|
||||||
|
private var orientationInfo: OrientationInfo
|
||||||
|
@EnvironmentObject
|
||||||
|
private var playbackInfo: ItemPlayback
|
||||||
|
|
||||||
@State private var isLoading: Bool = true;
|
@State
|
||||||
|
private var isLoading: Bool = true
|
||||||
|
|
||||||
var item: ResumeItem;
|
var item: ResumeItem
|
||||||
var fullItem: DetailItem;
|
var fullItem: DetailItem
|
||||||
|
|
||||||
@State private var progressString: String = "";
|
@State
|
||||||
@State private var viewDidLoad: Bool = false;
|
private var progressString: String = ""
|
||||||
@State private var watched: Bool = false {
|
@State
|
||||||
|
private var viewDidLoad: Bool = false
|
||||||
|
@State
|
||||||
|
private var watched: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if(watched == true) {
|
if watched == true {
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
print((globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)?DatePlayed=\(formatter.string(from: date).replacingOccurrences(of: ":", with: "%3A"))")
|
print((globalData.server?.baseURI ?? "") +
|
||||||
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)?DatePlayed=\(formatter.string(from: date).replacingOccurrences(of: ":", with: "%3A"))")
|
"/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)?DatePlayed=\(formatter.string(from: date).replacingOccurrences(of: ":", with: "%3A"))")
|
||||||
|
let request = RestRequest(method: .post,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)?DatePlayed=\(formatter.string(from: date).replacingOccurrences(of: ":", with: "%3A"))")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let request = RestRequest(method: .delete, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)")
|
let request = RestRequest(method: .delete,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/PlayedItems/\(fullItem.Id)")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
@State private var favorite: Bool = false {
|
|
||||||
|
@State
|
||||||
|
private var favorite: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if(favorite == true) {
|
if favorite == true {
|
||||||
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
let request = RestRequest(method: .post,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let request = RestRequest(method: .delete, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
let request = RestRequest(method: .delete,
|
||||||
|
url: (globalData.server?.baseURI ?? "") +
|
||||||
|
"/Users/\(globalData.user?.user_id ?? "")/FavoriteItems/\(fullItem.Id)")
|
||||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (_: Result<RestResponse<Data>, RestError>) in
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
init(item: ResumeItem) {
|
init(item: ResumeItem) {
|
||||||
self.item = item;
|
self.item = item
|
||||||
fullItem = DetailItem();
|
self.fullItem = DetailItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadData() {
|
func loadData() {
|
||||||
if(_viewDidLoad.wrappedValue == true) {
|
if _viewDidLoad.wrappedValue == true {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
_viewDidLoad.wrappedValue = true;
|
_viewDidLoad.wrappedValue = true
|
||||||
let url = "/Users/\(globalData.user?.user_id ?? "")/Items/\(item.Id)"
|
let url = "/Users/\(globalData.user?.user_id ?? "")/Items/\(item.Id)"
|
||||||
|
|
||||||
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
|
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
|
||||||
|
@ -136,9 +194,9 @@ struct MovieItemView: View {
|
||||||
request.contentType = "application/json"
|
request.contentType = "application/json"
|
||||||
request.acceptType = "application/json"
|
request.acceptType = "application/json"
|
||||||
|
|
||||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
request.responseData { (result: Result<RestResponse<Data>, RestError>) in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let response):
|
case let .success(response):
|
||||||
let body = response.body
|
let body = response.body
|
||||||
do {
|
do {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
|
@ -159,108 +217,120 @@ struct MovieItemView: View {
|
||||||
fullItem.SeriesName = json["SeriesName"].string ?? nil
|
fullItem.SeriesName = json["SeriesName"].string ?? nil
|
||||||
fullItem.Progress = Double(json["UserData"]["PlaybackPositionTicks"].int ?? 0)
|
fullItem.Progress = Double(json["UserData"]["PlaybackPositionTicks"].int ?? 0)
|
||||||
fullItem.OfficialRating = json["OfficialRating"].string ?? "PG-13"
|
fullItem.OfficialRating = json["OfficialRating"].string ?? "PG-13"
|
||||||
fullItem.Watched = json["UserData"]["Played"].bool ?? false;
|
fullItem.Watched = json["UserData"]["Played"].bool ?? false
|
||||||
fullItem.CommunityRating = String(json["CommunityRating"].float ?? 0.0);
|
fullItem.CommunityRating = String(json["CommunityRating"].float ?? 0.0)
|
||||||
fullItem.CriticRating = String(json["CriticRating"].int ?? 0);
|
fullItem.CriticRating = String(json["CriticRating"].int ?? 0)
|
||||||
fullItem.ParentId = json["ParentId"].string ?? ""
|
fullItem.ParentId = json["ParentId"].string ?? ""
|
||||||
//People
|
// People
|
||||||
fullItem.Directors = []
|
fullItem.Directors = []
|
||||||
fullItem.Studios = []
|
fullItem.Studios = []
|
||||||
fullItem.Writers = []
|
fullItem.Writers = []
|
||||||
fullItem.Cast = []
|
fullItem.Cast = []
|
||||||
fullItem.Genres = []
|
fullItem.Genres = []
|
||||||
|
|
||||||
for (_,person):(String, JSON) in json["People"] {
|
for (_, person): (String, JSON) in json["People"] {
|
||||||
if(person["Type"].stringValue == "Director") {
|
if person["Type"].stringValue == "Director" {
|
||||||
fullItem.Directors.append(person["Name"].string ?? "");
|
fullItem.Directors.append(person["Name"].string ?? "")
|
||||||
} else if(person["Type"].stringValue == "Writer") {
|
} else if person["Type"].stringValue == "Writer" {
|
||||||
fullItem.Writers.append(person["Name"].string ?? "");
|
fullItem.Writers.append(person["Name"].string ?? "")
|
||||||
} else if(person["Type"].stringValue == "Actor") {
|
} else if person["Type"].stringValue == "Actor" {
|
||||||
let cast = CastMember();
|
let cast = CastMember()
|
||||||
cast.Name = person["Name"].string ?? "";
|
cast.Name = person["Name"].string ?? ""
|
||||||
cast.Id = person["Id"].string ?? "";
|
cast.Id = person["Id"].string ?? ""
|
||||||
let imageTag = person["PrimaryImageTag"].string ?? "";
|
let imageTag = person["PrimaryImageTag"].string ?? ""
|
||||||
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? "";
|
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""
|
||||||
cast.Role = person["Role"].string ?? "";
|
cast.Role = person["Role"].string ?? ""
|
||||||
cast.Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxWidth=250&quality=85&tag=\(imageTag)")!
|
cast
|
||||||
fullItem.Cast.append(cast);
|
.Image =
|
||||||
|
URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxWidth=250&quality=85&tag=\(imageTag)")!
|
||||||
|
fullItem.Cast.append(cast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Studios
|
// Studios
|
||||||
for (_,studio):(String, JSON) in json["Studios"] {
|
for (_, studio): (String, JSON) in json["Studios"] {
|
||||||
fullItem.Studios.append(studio["Name"].string ?? "");
|
fullItem.Studios.append(studio["Name"].string ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Genres
|
// Genres
|
||||||
for (_,genre):(String, JSON) in json["GenreItems"] {
|
for (_, genre): (String, JSON) in json["GenreItems"] {
|
||||||
let tmpGenre = IVGenre()
|
let tmpGenre = IVGenre()
|
||||||
tmpGenre.Id = genre["Id"].string ?? "";
|
tmpGenre.Id = genre["Id"].string ?? ""
|
||||||
tmpGenre.Name = genre["Name"].string ?? "";
|
tmpGenre.Name = genre["Name"].string ?? ""
|
||||||
fullItem.Genres.append(tmpGenre);
|
fullItem.Genres.append(tmpGenre)
|
||||||
}
|
}
|
||||||
|
|
||||||
_watched.wrappedValue = fullItem.Watched
|
_watched.wrappedValue = fullItem.Watched
|
||||||
_favorite.wrappedValue = json["UserData"]["IsFavorite"].bool ?? false;
|
_favorite.wrappedValue = json["UserData"]["IsFavorite"].bool ?? false
|
||||||
|
|
||||||
//Process runtime
|
// Process runtime
|
||||||
let seconds: Int = ((json["RunTimeTicks"].int ?? 0)/10000000)
|
let seconds: Int = ((json["RunTimeTicks"].int ?? 0) / 10_000_000)
|
||||||
fullItem.RuntimeTicks = json["RunTimeTicks"].int ?? 0;
|
fullItem.RuntimeTicks = json["RunTimeTicks"].int ?? 0
|
||||||
let hours = (seconds/3600)
|
let hours = (seconds / 3600)
|
||||||
let minutes = ((seconds - (hours * 3600))/60)
|
let minutes = ((seconds - (hours * 3600)) / 60)
|
||||||
if(hours != 0) {
|
if hours != 0 {
|
||||||
fullItem.Runtime = "\(hours):\(String(minutes).leftPad(toWidth: 2, withString: "0"))"
|
fullItem.Runtime = "\(hours):\(String(minutes).leftPad(toWidth: 2, withString: "0"))"
|
||||||
} else {
|
} else {
|
||||||
fullItem.Runtime = "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m"
|
fullItem.Runtime = "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fullItem.Progress != 0) {
|
if fullItem.Progress != 0 {
|
||||||
let remainingSecs = (Double(json["RunTimeTicks"].int ?? 0) - fullItem.Progress)/10000000
|
let remainingSecs = (Double(json["RunTimeTicks"].int ?? 0) - fullItem.Progress) / 10_000_000
|
||||||
let proghours = Int(remainingSecs/3600)
|
let proghours = Int(remainingSecs / 3600)
|
||||||
let progminutes = Int((Int(remainingSecs) - (proghours * 3600))/60)
|
let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60)
|
||||||
if(proghours != 0) {
|
if proghours != 0 {
|
||||||
_progressString.wrappedValue = "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
|
_progressString.wrappedValue = "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
|
||||||
} else {
|
} else {
|
||||||
_progressString.wrappedValue = "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
_progressString.wrappedValue = "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_isLoading.wrappedValue = false;
|
_isLoading.wrappedValue = false
|
||||||
} catch {
|
} catch {}
|
||||||
|
case let .failure(error):
|
||||||
}
|
|
||||||
break
|
|
||||||
case .failure(let error):
|
|
||||||
debugPrint(error)
|
debugPrint(error)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
LoadingView(isShowing: $isLoading) {
|
LoadingView(isShowing: $isLoading) {
|
||||||
VStack(alignment:.leading) {
|
VStack(alignment: .leading) {
|
||||||
if(!isLoading) {
|
if !isLoading {
|
||||||
if(orientationInfo.orientation == .portrait) {
|
if orientationInfo.orientation == .portrait {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
VStack() {
|
VStack {
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
|
||||||
|
.BackdropBlurHash,
|
||||||
|
size: CGSize(width: 32, height: 32))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets
|
||||||
|
.trailing,
|
||||||
|
height: UIDevice.current
|
||||||
|
.userInterfaceIdiom == .pad ? 350 :
|
||||||
|
(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets
|
||||||
|
.trailing) * 0.5625)
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity(0.3)
|
.opacity(0.3)
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: UIDevice.current.userInterfaceIdiom == .pad ? 350 : (geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) * 0.5625)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||||
|
height: UIDevice.current
|
||||||
|
.userInterfaceIdiom == .pad ? 350 :
|
||||||
|
(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) *
|
||||||
|
0.5625)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.overlay(
|
.overlay(HStack {
|
||||||
HStack() {
|
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 32, height: 32))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
fullItem.PosterBlurHash,
|
||||||
|
size: CGSize(width: 32, height: 32))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 120, height: 180)
|
.frame(width: 120, height: 180)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
|
@ -274,7 +344,7 @@ struct MovieItemView: View {
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.offset(y: -4)
|
.offset(y: -4)
|
||||||
HStack() {
|
HStack {
|
||||||
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -283,19 +353,17 @@ struct MovieItemView: View {
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if(fullItem.OfficialRating != "") {
|
if fullItem.OfficialRating != "" {
|
||||||
Text(fullItem.OfficialRating).font(.subheadline)
|
Text(fullItem.OfficialRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||||
.overlay(
|
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||||
RoundedRectangle(cornerRadius: 2)
|
.stroke(Color.secondary, lineWidth: 1))
|
||||||
.stroke(Color.secondary, lineWidth: 1)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if(fullItem.CommunityRating != "0") {
|
if fullItem.CommunityRating != "0" {
|
||||||
HStack() {
|
HStack {
|
||||||
Image(systemName: "star").foregroundColor(.secondary)
|
Image(systemName: "star").foregroundColor(.secondary)
|
||||||
Text(fullItem.CommunityRating).font(.subheadline)
|
Text(fullItem.CommunityRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
@ -305,78 +373,99 @@ struct MovieItemView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}.offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46).padding(.trailing, 16)
|
}.offset(x: 0, y: UIDevice.current.userInterfaceIdiom == .pad ? -98 : -46)
|
||||||
}.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40)
|
.padding(.trailing, 16)
|
||||||
, alignment: .bottomLeading)
|
}.offset(x: 16, y: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40),
|
||||||
|
alignment: .bottomLeading)
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack() {
|
HStack {
|
||||||
//Play button
|
// Play button
|
||||||
Button {
|
Button {
|
||||||
self.playbackInfo.itemToPlay = fullItem;
|
self.playbackInfo.itemToPlay = fullItem
|
||||||
self.playbackInfo.shouldPlay = true;
|
self.playbackInfo.shouldPlay = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack() {
|
HStack {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left")
|
||||||
|
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
||||||
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
}
|
}
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}.buttonStyle(PlainButtonStyle())
|
}.buttonStyle(PlainButtonStyle())
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack() {
|
HStack {
|
||||||
Button() {
|
Button {
|
||||||
favorite.toggle()
|
favorite.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(!favorite) {
|
if !favorite {
|
||||||
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20))
|
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed))
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button() {
|
Button {
|
||||||
watched.toggle()
|
watched.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(watched) {
|
if watched {
|
||||||
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
ScrollView() {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if(fullItem.Tagline != "") {
|
if fullItem.Tagline != "" {
|
||||||
Text(fullItem.Tagline).font(.body).italic().padding(.top, 7).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,16)
|
Text(fullItem.Tagline).font(.body).italic().padding(.top, 7)
|
||||||
|
.fixedSize(horizontal: false, vertical: true).padding(.leading, 16)
|
||||||
|
.padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,16)
|
Text(fullItem.Overview).font(.footnote).padding(.top, 3)
|
||||||
if(fullItem.Genres.count != 0) {
|
.fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16)
|
||||||
|
.padding(.trailing, 16)
|
||||||
|
if !fullItem.Genres.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
ForEach(fullItem.Genres, id: \.Id) { genre in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(genres: [
|
||||||
|
genre
|
||||||
|
.Name,
|
||||||
|
])),
|
||||||
|
title: genre.Name)) {
|
||||||
Text(genre.Name).font(.footnote)
|
Text(genre.Name).font(.footnote)
|
||||||
// }
|
|
||||||
}
|
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fullItem.Cast.count != 0) {
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fullItem.Cast.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
VStack() {
|
VStack {
|
||||||
Spacer().frame(height: 8);
|
Spacer().frame(height: 8)
|
||||||
HStack() {
|
HStack {
|
||||||
Spacer().frame(width: 16)
|
Spacer().frame(width: 16)
|
||||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(personIds: [
|
||||||
VStack() {
|
cast
|
||||||
|
.Id,
|
||||||
|
])), title: cast.Name)) {
|
||||||
|
VStack {
|
||||||
WebImage(url: cast.Image)
|
WebImage(url: cast.Image)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!)
|
Image(uiImage: UIImage(blurHash: cast
|
||||||
|
.ImageBlurHash == "" ?
|
||||||
|
"W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
cast.ImageBlurHash,
|
||||||
|
size: CGSize(width: 16,
|
||||||
|
height: 16))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
|
@ -385,12 +474,14 @@ struct MovieItemView: View {
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1)
|
||||||
if(cast.Role != "") {
|
.frame(width: 100).foregroundColor(Color.primary)
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
if cast.Role != "" {
|
||||||
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1)
|
||||||
|
.foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
Spacer().frame(width: 10)
|
Spacer().frame(width: 10)
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 16)
|
Spacer().frame(width: 16)
|
||||||
|
@ -398,51 +489,66 @@ struct MovieItemView: View {
|
||||||
}
|
}
|
||||||
}.padding(.top, -3)
|
}.padding(.top, -3)
|
||||||
}
|
}
|
||||||
if(fullItem.Directors.count != 0) {
|
if !fullItem.Directors.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Directors:").font(.callout).fontWeight(.semibold)
|
Text("Directors:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
if(fullItem.Writers.count != 0) {
|
if !fullItem.Writers.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Writers:").font(.callout).fontWeight(.semibold)
|
Text("Writers:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
if(fullItem.Studios.count != 0) {
|
if !fullItem.Studios.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,16)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16).padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 3)
|
Spacer().frame(height: 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0, trailing: 0))
|
}
|
||||||
|
.padding(EdgeInsets(top: UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24, leading: 0, bottom: 0,
|
||||||
|
trailing: 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
ZStack() {
|
ZStack {
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=750&quality=90&tag=\(fullItem.Backdrop)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 16, height: 16))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
|
||||||
|
.BackdropBlurHash,
|
||||||
|
size: CGSize(width: 16, height: 16))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets
|
||||||
|
.trailing,
|
||||||
|
height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets
|
||||||
|
.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity(0.3)
|
.opacity(0.3)
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||||
|
height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
HStack() {
|
HStack {
|
||||||
VStack() {
|
VStack {
|
||||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (fullItem.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash), size: CGSize(width: 16, height: 16))!)
|
Image(uiImage: UIImage(blurHash: fullItem
|
||||||
|
.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
fullItem.PosterBlurHash,
|
||||||
|
size: CGSize(width: 16, height: 16))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 120, height: 180)
|
.frame(width: 120, height: 180)
|
||||||
}
|
}
|
||||||
|
@ -451,23 +557,24 @@ struct MovieItemView: View {
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
Spacer().frame(height: 15)
|
Spacer().frame(height: 15)
|
||||||
Button {
|
Button {
|
||||||
self.playbackInfo.itemToPlay = fullItem;
|
self.playbackInfo.itemToPlay = fullItem
|
||||||
self.playbackInfo.shouldPlay = true;
|
self.playbackInfo.shouldPlay = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack() {
|
HStack {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left")
|
||||||
|
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
||||||
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
}
|
}
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}.buttonStyle(PlainButtonStyle())
|
}.buttonStyle(PlainButtonStyle())
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
ScrollView() {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack() {
|
HStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(fullItem.Name).font(.headline)
|
Text(fullItem.Name).font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
@ -475,7 +582,7 @@ struct MovieItemView: View {
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.offset(x: 14, y: 0)
|
.offset(x: 14, y: 0)
|
||||||
Spacer().frame(height: 1)
|
Spacer().frame(height: 1)
|
||||||
HStack() {
|
HStack {
|
||||||
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -484,19 +591,17 @@ struct MovieItemView: View {
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if(fullItem.OfficialRating != "") {
|
if fullItem.OfficialRating != "" {
|
||||||
Text(fullItem.OfficialRating).font(.subheadline)
|
Text(fullItem.OfficialRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||||
.overlay(
|
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||||
RoundedRectangle(cornerRadius: 2)
|
.stroke(Color.secondary, lineWidth: 1))
|
||||||
.stroke(Color.secondary, lineWidth: 1)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if(fullItem.CommunityRating != "0") {
|
if fullItem.CommunityRating != "0" {
|
||||||
HStack() {
|
HStack {
|
||||||
Image(systemName: "star").foregroundColor(.secondary)
|
Image(systemName: "star").foregroundColor(.secondary)
|
||||||
Text(fullItem.CommunityRating).font(.subheadline)
|
Text(fullItem.CommunityRating).font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
@ -510,56 +615,77 @@ struct MovieItemView: View {
|
||||||
.offset(x: 14)
|
.offset(x: 14)
|
||||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack() {
|
HStack {
|
||||||
Button() {
|
Button {
|
||||||
favorite.toggle()
|
favorite.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(!favorite) {
|
if !favorite {
|
||||||
Image(systemName: "heart").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "heart").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed)).font(.system(size: 20))
|
Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed))
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button() {
|
Button {
|
||||||
watched.toggle()
|
watched.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
if(watched) {
|
if watched {
|
||||||
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "checkmark.rectangle.fill").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary).font(.system(size: 20))
|
Image(systemName: "xmark.rectangle").foregroundColor(Color.primary)
|
||||||
|
.font(.system(size: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
}.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
if(fullItem.Tagline != "") {
|
if fullItem.Tagline != "" {
|
||||||
Text(fullItem.Tagline).font(.body).italic().padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
Text(fullItem.Tagline).font(.body).italic().padding(.top, 3)
|
||||||
|
.fixedSize(horizontal: false, vertical: true).padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
Text(fullItem.Overview).font(.footnote).padding(.top, 3).fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
Text(fullItem.Overview).font(.footnote).padding(.top, 3)
|
||||||
if(fullItem.Genres.count != 0) {
|
.fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
if !fullItem.Genres.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
ForEach(fullItem.Genres, id: \.Id) { genre in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(genres: [
|
||||||
|
genre
|
||||||
|
.Name,
|
||||||
|
])),
|
||||||
|
title: genre.Name)) {
|
||||||
Text(genre.Name).font(.footnote)
|
Text(genre.Name).font(.footnote)
|
||||||
// }
|
|
||||||
}
|
|
||||||
}.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fullItem.Cast.count != 0) {
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fullItem.Cast.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
VStack() {
|
VStack {
|
||||||
Spacer().frame(height: 8);
|
Spacer().frame(height: 8)
|
||||||
HStack() {
|
HStack {
|
||||||
Spacer().frame(width: 16)
|
Spacer().frame(width: 16)
|
||||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(personIds: [
|
||||||
VStack() {
|
cast
|
||||||
|
.Id,
|
||||||
|
])), title: cast.Name)) {
|
||||||
|
VStack {
|
||||||
WebImage(url: cast.Image)
|
WebImage(url: cast.Image)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!)
|
Image(uiImage: UIImage(blurHash: cast
|
||||||
|
.ImageBlurHash == "" ?
|
||||||
|
"W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" :
|
||||||
|
cast.ImageBlurHash,
|
||||||
|
size: CGSize(width: 16,
|
||||||
|
height: 16))!)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
|
@ -568,41 +694,51 @@ struct MovieItemView: View {
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1)
|
||||||
if(cast.Role != "") {
|
.frame(width: 100).foregroundColor(Color.primary)
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
if cast.Role != "" {
|
||||||
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1)
|
||||||
|
.foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
Spacer().frame(width: 10)
|
Spacer().frame(width: 10)
|
||||||
}
|
}
|
||||||
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding(.top, -3).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? -55 : 0)
|
}.padding(.top, -3)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? -55 : 0)
|
||||||
}
|
}
|
||||||
if(fullItem.Directors.count != 0) {
|
if !fullItem.Directors.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Directors:").font(.callout).fontWeight(.semibold)
|
Text("Directors:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
if(fullItem.Writers.count != 0) {
|
if !fullItem.Writers.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Writers:").font(.callout).fontWeight(.semibold)
|
Text("Writers:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
if(fullItem.Studios.count != 0) {
|
if !fullItem.Studios.isEmpty {
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1).foregroundColor(Color.secondary)
|
Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1)
|
||||||
}.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
.foregroundColor(Color.secondary)
|
||||||
|
}.padding(.leading, 16)
|
||||||
|
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 195);
|
Spacer().frame(height: 195)
|
||||||
}.frame(maxHeight: .infinity)
|
}.frame(maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
}.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55).edgesIgnoringSafeArea(.leading)
|
}.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||||
|
.edgesIgnoringSafeArea(.leading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ struct NextUpView: View {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
for (_,item):(String, JSON) in json["Items"] {
|
for (_,item):(String, JSON) in json["Items"] {
|
||||||
// Do something you want
|
// Do something you want
|
||||||
let itemObj = ResumeItem()
|
var itemObj = ResumeItem()
|
||||||
itemObj.Image = item["SeriesPrimaryImageTag"].string ?? ""
|
itemObj.Image = item["SeriesPrimaryImageTag"].string ?? ""
|
||||||
itemObj.ImageType = "Primary"
|
itemObj.ImageType = "Primary"
|
||||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||||
|
|
|
@ -113,7 +113,7 @@ struct SeasonItemView: View {
|
||||||
episode.ParentId = episode.SeasonId ?? "";
|
episode.ParentId = episode.SeasonId ?? "";
|
||||||
episode.CommunityRating = String(json["CommunityRating"].float ?? 0.0)
|
episode.CommunityRating = String(json["CommunityRating"].float ?? 0.0)
|
||||||
|
|
||||||
let rI = ResumeItem()
|
var rI = ResumeItem()
|
||||||
rI.Name = episode.Name;
|
rI.Name = episode.Name;
|
||||||
rI.Id = episode.Id;
|
rI.Id = episode.Id;
|
||||||
rI.IndexNumber = episode.IndexNumber;
|
rI.IndexNumber = episode.IndexNumber;
|
||||||
|
|
|
@ -37,7 +37,7 @@ struct SeriesItemView: View {
|
||||||
let json = try JSON(data: body)
|
let json = try JSON(data: body)
|
||||||
for (_,item):(String, JSON) in json["Items"] {
|
for (_,item):(String, JSON) in json["Items"] {
|
||||||
// Do something you want
|
// Do something you want
|
||||||
let itemObj = ResumeItem()
|
var itemObj = ResumeItem()
|
||||||
itemObj.Type = "Season"
|
itemObj.Type = "Season"
|
||||||
itemObj.Id = item["Id"].string ?? ""
|
itemObj.Id = item["Id"].string ?? ""
|
||||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||||
|
|
|
@ -101,17 +101,14 @@ struct SettingsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigationBarTitle("Settings", displayMode: .inline)
|
.navigationBarTitle("Settings", displayMode: .inline)
|
||||||
.toolbar {
|
.navigationBarItems(leading:
|
||||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
|
||||||
Button {
|
Button {
|
||||||
close = false
|
close = false
|
||||||
} label: {
|
} label: {
|
||||||
HStack() {
|
HStack() {
|
||||||
Text("Back").font(.callout)
|
Text("Back").font(.callout)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onAppear(perform: onAppear)
|
}.onAppear(perform: onAppear)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue