parent
d9f8fb5e42
commit
3b38a20625
|
@ -41,9 +41,12 @@
|
|||
53E4E649263F725B00F67C6B /* MultiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelector.swift */; };
|
||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
|
||||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
|
||||
6213388E265F777C00A81A2A /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388D265F777C00A81A2A /* LibraryViewModel.swift */; };
|
||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* LibraryListView.swift */; };
|
||||
6273DD43265F4195009C1D0B /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD42265F4195009C1D0B /* Moya */; };
|
||||
6273DD45265F4195009C1D0B /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD44265F4195009C1D0B /* CombineMoya */; };
|
||||
6273DD48265F41B3009C1D0B /* JellyfinAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6273DD47265F41B3009C1D0B /* JellyfinAPI.swift */; };
|
||||
6273DD4E265F47B2009C1D0B /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */; };
|
||||
AE8C3154265D60BF008AA076 /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8C3153265D60BF008AA076 /* SettingsModel.swift */; };
|
||||
AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8C3155265D616A008AA076 /* SettingsViewModel.swift */; };
|
||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||
|
@ -103,7 +106,10 @@
|
|||
53E4E648263F725B00F67C6B /* MultiSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelector.swift; sourceTree = "<group>"; };
|
||||
53EE24E5265060780068F029 /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
||||
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = "<group>"; };
|
||||
6213388D265F777C00A81A2A /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = "<group>"; };
|
||||
6213388F265F83A900A81A2A /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
|
||||
6273DD47265F41B3009C1D0B /* JellyfinAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPI.swift; sourceTree = "<group>"; };
|
||||
6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = "<group>"; };
|
||||
AE8C3153265D60BF008AA076 /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = "<group>"; };
|
||||
AE8C3155265D616A008AA076 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||
|
@ -150,6 +156,7 @@
|
|||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6273DD4A265F4794009C1D0B /* Domains */,
|
||||
6273DD46265F419B009C1D0B /* APIs */,
|
||||
AE8C3157265D6F5E008AA076 /* Resources */,
|
||||
AE8C3152265D607B008AA076 /* ViewModels */,
|
||||
|
@ -179,6 +186,7 @@
|
|||
53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
|
||||
53987CA72657424A00E7EA70 /* EpisodeItemView.swift */,
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
|
||||
6213388F265F83A900A81A2A /* LibraryListView.swift */,
|
||||
);
|
||||
path = JellyfinPlayer;
|
||||
sourceTree = "<group>";
|
||||
|
@ -199,6 +207,22 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6213388B265F776B00A81A2A /* Library */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6213388C265F777100A81A2A /* ViewModels */,
|
||||
);
|
||||
path = Library;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6213388C265F777100A81A2A /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6213388D265F777C00A81A2A /* LibraryViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6273DD46265F419B009C1D0B /* APIs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -207,6 +231,31 @@
|
|||
path = APIs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6273DD49265F478E009C1D0B /* Search */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6273DD4B265F479B009C1D0B /* ViewModels */,
|
||||
);
|
||||
path = Search;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6273DD4A265F4794009C1D0B /* Domains */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6213388B265F776B00A81A2A /* Library */,
|
||||
6273DD49265F478E009C1D0B /* Search */,
|
||||
);
|
||||
path = Domains;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6273DD4B265F479B009C1D0B /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AE8C3150265D5FE1008AA076 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -342,6 +391,7 @@
|
|||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||
53987CA426572C1300E7EA70 /* SeasonItemView.swift in Sources */,
|
||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
||||
AE8C3154265D60BF008AA076 /* SettingsModel.swift in Sources */,
|
||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
||||
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
|
||||
|
@ -350,6 +400,7 @@
|
|||
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
|
||||
53E4E649263F725B00F67C6B /* MultiSelector.swift in Sources */,
|
||||
53E4E647263F6CF100F67C6B /* LibraryFilterView.swift in Sources */,
|
||||
6213388E265F777C00A81A2A /* LibraryViewModel.swift in Sources */,
|
||||
6273DD48265F41B3009C1D0B /* JellyfinAPI.swift in Sources */,
|
||||
53892777263CBB000035E14B /* JellyApiTypings.swift in Sources */,
|
||||
5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */,
|
||||
|
@ -359,6 +410,7 @@
|
|||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||
AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */,
|
||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||
6273DD4E265F47B2009C1D0B /* LibrarySearchViewModel.swift in Sources */,
|
||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
|
||||
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,
|
||||
|
|
|
@ -8,8 +8,26 @@
|
|||
import Foundation
|
||||
import Moya
|
||||
|
||||
enum ImageType: String {
|
||||
case primary = "Primary"
|
||||
case backdrop = "Backdrop"
|
||||
case thumb = "Thumb"
|
||||
case banner = "Banner"
|
||||
}
|
||||
|
||||
enum Field: String {
|
||||
case primaryImageAspectRatio = "PrimaryImageAspectRatio"
|
||||
case basicSyncInfo = "BasicSyncInfo"
|
||||
}
|
||||
|
||||
enum ItemType: String {
|
||||
case movie = "Movie"
|
||||
case series = "Series"
|
||||
}
|
||||
|
||||
enum SortType: String {
|
||||
case name = "Name"
|
||||
case dateCreated = "DateCreated"
|
||||
}
|
||||
|
||||
enum ASC: String {
|
||||
|
@ -17,25 +35,67 @@ enum ASC: String {
|
|||
case ascending = "Ascending"
|
||||
}
|
||||
|
||||
enum FilterType: String {
|
||||
case isFavorite = "IsFavorite"
|
||||
}
|
||||
|
||||
struct Filter {
|
||||
var imageTypes = [ImageType]()
|
||||
var fields = [Field]()
|
||||
var itemTypes = [ItemType]()
|
||||
var filterTypes = [FilterType]()
|
||||
var sort: SortType?
|
||||
var asc: ASC?
|
||||
var parentID: String?
|
||||
var imageTypeLimit: Int?
|
||||
var recursive = true
|
||||
var genres = [String]()
|
||||
var personIds = [String]()
|
||||
}
|
||||
|
||||
extension Filter {
|
||||
var toParamters: [String: Any] {
|
||||
var parameters = [String: Any]()
|
||||
parameters["EnableImageTypes"] = imageTypes.map(\.rawValue).joined(separator: ",")
|
||||
parameters["Fields"] = fields.map(\.rawValue).joined(separator: ",")
|
||||
parameters["Filters"] = filterTypes.map(\.rawValue).joined(separator: ",")
|
||||
parameters["ImageTypeLimit"] = imageTypeLimit
|
||||
parameters["IncludeItemTypes"] = itemTypes.map(\.rawValue).joined(separator: ",")
|
||||
parameters["ParentId"] = parentID
|
||||
parameters["Recursive"] = recursive
|
||||
parameters["SortBy"] = sort?.rawValue
|
||||
parameters["SortOrder"] = asc?.rawValue
|
||||
parameters["Genres"] = genres.joined(separator: ",")
|
||||
parameters["PersonIds"] = personIds.joined(separator: ",")
|
||||
return parameters
|
||||
}
|
||||
}
|
||||
|
||||
enum JellyfinAPI {
|
||||
case search(globalData: GlobalData, url: URL, query: String, sort: SortType = .name, asc: ASC = .descending)
|
||||
case items(globalData: GlobalData, filter: Filter, page: Int)
|
||||
case search(globalData: GlobalData, filter: Filter, searchQuery: String, page: Int)
|
||||
}
|
||||
|
||||
extension JellyfinAPI: TargetType {
|
||||
var baseURL: URL {
|
||||
switch self {
|
||||
case let .search(_, url, _, _, _):
|
||||
return url
|
||||
case let .items(global, _, _),
|
||||
let .search(global, _, _, _):
|
||||
return URL(string: global.server?.baseURI ?? "")!
|
||||
}
|
||||
}
|
||||
|
||||
var path: String {
|
||||
return ""
|
||||
switch self {
|
||||
case let .items(global, _, _),
|
||||
let .search(global, _, _, _):
|
||||
return "/Users/\(global.user?.user_id ?? "")/Items"
|
||||
}
|
||||
}
|
||||
|
||||
var method: Moya.Method {
|
||||
switch self {
|
||||
case .search:
|
||||
case .items, .search:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
@ -46,19 +106,27 @@ extension JellyfinAPI: TargetType {
|
|||
|
||||
var task: Task {
|
||||
switch self {
|
||||
case let .search(_, _, query, sort, asc):
|
||||
var parameters = [String: Any]()
|
||||
parameters["searchTerm"] = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
|
||||
parameters["SortBy"] = sort.rawValue
|
||||
parameters["SortOrder"] = asc.rawValue
|
||||
case let .search(_, filter, searchQuery, page):
|
||||
var parameters = filter.toParamters
|
||||
parameters["searchTerm"] = searchQuery.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
|
||||
parameters["StartIndex"] = page * 100
|
||||
parameters["Limit"] = 100
|
||||
return .requestParameters(parameters: parameters, encoding: JSONEncoding.default)
|
||||
case let .items(_, filter, page):
|
||||
var parameters = filter.toParamters
|
||||
parameters["StartIndex"] = page * 100
|
||||
parameters["Limit"] = 100
|
||||
return .requestParameters(parameters: parameters, encoding: JSONEncoding.default)
|
||||
}
|
||||
}
|
||||
|
||||
var headers: [String: String]? {
|
||||
switch self {
|
||||
case let .search(globalData, _, _, _, _):
|
||||
return ["X-Emby-Authorization": globalData.authHeader]
|
||||
case let .items(global, _, _),
|
||||
let .search(global, _, _, _):
|
||||
return [
|
||||
"X-Emby-Authorization": global.authHeader,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,9 +197,9 @@ struct ContentView: View {
|
|||
HStack() {
|
||||
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
||||
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(prefill: library_id, names: [library_id: library_names[library_id] ?? ""], libraries: [library_id], filter: "&SortBy=DateCreated&SortOrder=Descending")) {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
}
|
||||
// }
|
||||
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||
LatestMediaView(library: library_id)
|
||||
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||
|
@ -224,9 +224,8 @@ struct ContentView: View {
|
|||
Image(systemName: "house")
|
||||
})
|
||||
.tag("Home")
|
||||
|
||||
NavigationView() {
|
||||
LibraryView(prefill: "", names: library_names, libraries: libraries)
|
||||
NavigationView {
|
||||
LibraryListView(libraryNames: library_names, libraryIDs: libraries)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.tabItem({
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// LibraryViewModel.swift
|
||||
// JellyfinPlayer
|
||||
//
|
||||
// Created by PangMo5 on 2021/05/27.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import CombineMoya
|
||||
import Foundation
|
||||
import Moya
|
||||
import SwiftyJSON
|
||||
|
||||
final class LibraryViewModel: ObservableObject {
|
||||
fileprivate var provider = MoyaProvider<JellyfinAPI>(plugins: [NetworkLoggerPlugin(configuration: NetworkLoggerPlugin.Configuration(logOptions: .verbose))])
|
||||
|
||||
var prefillID: String
|
||||
@Published
|
||||
var filter: Filter
|
||||
|
||||
@Published
|
||||
var items = [ResumeItem]()
|
||||
|
||||
@Published
|
||||
var isLoading: Bool = true
|
||||
|
||||
@Published
|
||||
var isHiddenPreviousButton = true
|
||||
@Published
|
||||
var isHiddenNextButton = true
|
||||
|
||||
var page = 1
|
||||
|
||||
var globalData = GlobalData()
|
||||
|
||||
fileprivate var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(prefillID: String,
|
||||
filter: Filter? = nil)
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func requestNextPage() {
|
||||
page += 1
|
||||
requestItems()
|
||||
}
|
||||
|
||||
func requestPreviousPage() {
|
||||
page -= 1
|
||||
requestItems()
|
||||
}
|
||||
|
||||
func requestInitItems() {
|
||||
page = 1
|
||||
requestItems()
|
||||
}
|
||||
|
||||
fileprivate func requestItems() {
|
||||
isLoading = true
|
||||
provider.requestPublisher(.items(globalData: globalData, filter: filter, page: page))
|
||||
// .map(ResumeItem.self) TO DO
|
||||
.print()
|
||||
.sink(receiveCompletion: { _ in
|
||||
self.isLoading = false
|
||||
}, receiveValue: { response in
|
||||
self.items.removeAll()
|
||||
let body = response.data
|
||||
var totalCount = 0
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
totalCount = json["TotalRecordCount"].int ?? 0
|
||||
for (_, item): (String, JSON) in json["Items"] {
|
||||
// Do something you want
|
||||
let itemObj = ResumeItem()
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
if itemObj.Type == "Series" {
|
||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = nil
|
||||
itemObj.SeasonId = nil
|
||||
itemObj.SeriesId = nil
|
||||
itemObj.SeriesName = nil
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
} else {
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = item["ParentIndexNumber"].int ?? nil
|
||||
itemObj.SeasonId = item["SeasonId"].string ?? nil
|
||||
itemObj.SeriesId = item["SeriesId"].string ?? nil
|
||||
itemObj.SeriesName = item["SeriesName"].string ?? nil
|
||||
}
|
||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||
|
||||
self.items.append(itemObj)
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if totalCount > 100 {
|
||||
if self.page > 1 {
|
||||
self.isHiddenPreviousButton = false
|
||||
}
|
||||
if totalCount > (self.page * 100) {
|
||||
self.isHiddenNextButton = false
|
||||
}
|
||||
} else {
|
||||
self.isHiddenNextButton = true
|
||||
self.isHiddenPreviousButton = true
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// LibrarySearchViewModel.swift
|
||||
// JellyfinPlayer
|
||||
//
|
||||
// Created by PangMo5 on 2021/05/27.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import CombineMoya
|
||||
import Foundation
|
||||
import Moya
|
||||
import SwiftyJSON
|
||||
|
||||
final class LibrarySearchViewModel: ObservableObject {
|
||||
fileprivate var provider = MoyaProvider<JellyfinAPI>(plugins: [NetworkLoggerPlugin()])
|
||||
|
||||
var filter: Filter
|
||||
|
||||
@Published
|
||||
var items = [ResumeItem]()
|
||||
|
||||
@Published
|
||||
var searchQuery = ""
|
||||
@Published
|
||||
var isLoading: Bool = true
|
||||
|
||||
var page = 1
|
||||
|
||||
var globalData = GlobalData() {
|
||||
didSet {
|
||||
injectEnvironmentData()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(filter: Filter) {
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
fileprivate func injectEnvironmentData() {
|
||||
cancellables.removeAll()
|
||||
|
||||
$searchQuery
|
||||
.sink(receiveValue: requestSearch(query:))
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
fileprivate func requestSearch(query: String) {
|
||||
isLoading = true
|
||||
provider.requestPublisher(.search(globalData: globalData, filter: filter, searchQuery: query, page: page))
|
||||
// .map(ResumeItem.self) TO DO
|
||||
.print()
|
||||
.sink(receiveCompletion: { _ in
|
||||
self.isLoading = false
|
||||
}, receiveValue: { response in
|
||||
let body = response.data
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
for (_, item): (String, JSON) in json["Items"] {
|
||||
// Do something you want
|
||||
let itemObj = ResumeItem()
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
if itemObj.Type == "Series" {
|
||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = nil
|
||||
itemObj.SeasonId = nil
|
||||
itemObj.SeriesId = nil
|
||||
itemObj.SeriesName = nil
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
} else {
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = item["ParentIndexNumber"].int ?? nil
|
||||
itemObj.SeasonId = item["SeasonId"].string ?? nil
|
||||
itemObj.SeriesId = item["SeriesId"].string ?? nil
|
||||
itemObj.SeriesName = item["SeriesName"].string ?? nil
|
||||
}
|
||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||
|
||||
self.items.append(itemObj)
|
||||
}
|
||||
} catch {}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
|
@ -309,9 +309,9 @@ struct EpisodeItemView: View {
|
|||
HStack() {
|
||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
Text(genre.Name).font(.footnote)
|
||||
}
|
||||
// }
|
||||
}
|
||||
}.padding(.leading, 16).padding(.trailing,16)
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ struct EpisodeItemView: View {
|
|||
HStack() {
|
||||
Spacer().frame(width: 16)
|
||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
VStack() {
|
||||
WebImage(url: cast.Image)
|
||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||
|
@ -342,7 +342,7 @@ struct EpisodeItemView: View {
|
|||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
Spacer().frame(width: 10)
|
||||
}
|
||||
Spacer().frame(width: 16)
|
||||
|
@ -493,9 +493,9 @@ struct EpisodeItemView: View {
|
|||
HStack() {
|
||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
Text(genre.Name).font(.footnote)
|
||||
}
|
||||
// }
|
||||
}
|
||||
}.padding(.leading, 16).padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||
}
|
||||
|
@ -507,7 +507,7 @@ struct EpisodeItemView: View {
|
|||
HStack() {
|
||||
Spacer().frame(width: 16)
|
||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
VStack() {
|
||||
WebImage(url: cast.Image)
|
||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||
|
@ -526,7 +526,7 @@ struct EpisodeItemView: View {
|
|||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
Spacer().frame(width: 10)
|
||||
}
|
||||
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||
|
|
|
@ -11,7 +11,7 @@ class justSignedIn: ObservableObject {
|
|||
@Published var did: Bool = false
|
||||
}
|
||||
|
||||
class GlobalData: ObservableObject {
|
||||
class GlobalData: ObservableObject {
|
||||
@Published var user: SignedInUser?
|
||||
@Published var authToken: String = ""
|
||||
@Published var server: Server?
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// LibraryListView.swift
|
||||
// JellyfinPlayer
|
||||
//
|
||||
// Created by PangMo5 on 2021/05/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct LibraryListView: View {
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
@State
|
||||
private var libraryIDs: [String] = []
|
||||
@State
|
||||
private var libraryNames: [String: String] = [:]
|
||||
@State
|
||||
private var viewDidLoad: Bool = false
|
||||
@State
|
||||
private var closeSearch: Bool = false
|
||||
|
||||
init(libraryNames: [String: String], libraryIDs: [String]) {
|
||||
self._libraryNames = State(initialValue: libraryNames)
|
||||
self._libraryIDs = State(initialValue: libraryIDs)
|
||||
}
|
||||
|
||||
func listOnAppear() {
|
||||
if viewDidLoad == false {
|
||||
viewDidLoad = true
|
||||
libraryIDs.append("favorites")
|
||||
libraryNames["favorites"] = "Favorites"
|
||||
|
||||
libraryIDs.append("genres")
|
||||
libraryNames["genres"] = "Genres - WIP"
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List(libraryIDs, id: \.self) { id in
|
||||
if id != "genres" {
|
||||
NavigationLink(destination: LibraryView(viewModel: .init(prefillID: id), title: libraryNames[id] ?? "")) {
|
||||
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||
}
|
||||
} else {
|
||||
// NavigationLink(destination: LibraryView(prefill: id, names: libraryNames, libraries: library_ids)) {
|
||||
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||
// }
|
||||
}
|
||||
}
|
||||
.onAppear(perform: listOnAppear)
|
||||
.navigationTitle("All Media")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
// NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: .init()),
|
||||
// close: $closeSearch),
|
||||
// isActive: $closeSearch) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,193 +5,156 @@
|
|||
// Created by Aiden Vigue on 5/2/21.
|
||||
//
|
||||
|
||||
import SDWebImageSwiftUI
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
import SwiftyRequest
|
||||
import SDWebImageSwiftUI
|
||||
|
||||
struct LibrarySearchView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
|
||||
@State var url: String;
|
||||
@Binding var close: Bool;
|
||||
@State var open: Bool = false;
|
||||
@State private var isLoading: Bool = true;
|
||||
@State private var onlyUnplayed: Bool = false;
|
||||
@State private var viewDidLoad: Bool = false;
|
||||
@State var items: [ResumeItem] = []
|
||||
@State var linkedItem: ResumeItem = ResumeItem();
|
||||
@State var searchQuery: String = "" {
|
||||
didSet {
|
||||
self.onAppear();
|
||||
}
|
||||
};
|
||||
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
|
||||
@ObservedObject
|
||||
var viewModel: LibrarySearchViewModel
|
||||
|
||||
@Binding
|
||||
var close: Bool
|
||||
@State
|
||||
var open: Bool = false
|
||||
@State
|
||||
private var onlyUnplayed: Bool = false
|
||||
@State
|
||||
private var viewDidLoad: Bool = false
|
||||
@State
|
||||
var linkedItem = ResumeItem()
|
||||
|
||||
func onAppear() {
|
||||
recalcTracks()
|
||||
_isLoading.wrappedValue = true;
|
||||
_items.wrappedValue = [];
|
||||
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + _url.wrappedValue + "&searchTerm=" + searchQuery.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + (_url.wrappedValue.contains("SortBy") ? "" : "&SortBy=Name&SortOrder=Descending"))
|
||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||
request.contentType = "application/json"
|
||||
request.acceptType = "application/json"
|
||||
|
||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let body = response.body
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
for (_,item):(String, JSON) in json["Items"] {
|
||||
// Do something you want
|
||||
let itemObj = ResumeItem()
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
if(itemObj.Type == "Series") {
|
||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = nil
|
||||
itemObj.SeasonId = nil
|
||||
itemObj.SeriesId = nil
|
||||
itemObj.SeriesName = nil
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
} else {
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = item["ParentIndexNumber"].int ?? nil
|
||||
itemObj.SeasonId = item["SeasonId"].string ?? nil
|
||||
itemObj.SeriesId = item["SeriesId"].string ?? nil
|
||||
itemObj.SeriesName = item["SeriesName"].string ?? nil
|
||||
}
|
||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||
|
||||
_items.wrappedValue.append(itemObj)
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
break
|
||||
case .failure(let error):
|
||||
debugPrint(error)
|
||||
break
|
||||
}
|
||||
isLoading = false;
|
||||
}
|
||||
viewModel.globalData = globalData
|
||||
}
|
||||
|
||||
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
@Environment(\.verticalSizeClass)
|
||||
var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass)
|
||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
var isPortrait: Bool {
|
||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
func recalcTracks() {
|
||||
let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125));
|
||||
let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125))
|
||||
_tracks.wrappedValue = []
|
||||
for _ in (0..<trkCnt)
|
||||
{
|
||||
_tracks.wrappedValue.append(GridItem.init(.flexible()))
|
||||
for _ in 0 ..< trkCnt {
|
||||
_tracks.wrappedValue.append(GridItem(.flexible()))
|
||||
}
|
||||
}
|
||||
|
||||
@State private var tracks: [GridItem] = []
|
||||
|
||||
|
||||
@State
|
||||
private var tracks: [GridItem] = []
|
||||
|
||||
var body: some View {
|
||||
VStack() {
|
||||
NavigationLink(destination: ItemView(item: linkedItem), isActive: $open) {
|
||||
EmptyView();
|
||||
};
|
||||
Spacer().frame(height:6);
|
||||
TextField("Search", text: $searchQuery, onEditingChanged: { _ in
|
||||
print("changed")
|
||||
}, onCommit: {
|
||||
self.onAppear()
|
||||
})
|
||||
.padding(.horizontal, 10)
|
||||
.foregroundColor(Color.secondary)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
LoadingView(isShowing: $isLoading) {
|
||||
ZStack {
|
||||
VStack {
|
||||
NavigationLink(destination: ItemView(item: linkedItem), isActive: $open) {
|
||||
EmptyView()
|
||||
}
|
||||
Spacer().frame(height: 6)
|
||||
TextField("Search", text: $viewModel.searchQuery, onEditingChanged: { _ in
|
||||
print("changed")
|
||||
})
|
||||
.padding(.horizontal, 10)
|
||||
.foregroundColor(Color.secondary)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.Id) { item in
|
||||
Button() {
|
||||
_linkedItem.wrappedValue = item;
|
||||
_close.wrappedValue = false;
|
||||
_open.wrappedValue = true;
|
||||
ForEach(viewModel.items, id: \.Id) { item in
|
||||
Button {
|
||||
_linkedItem.wrappedValue = item
|
||||
_close.wrappedValue = false
|
||||
_open.wrappedValue = true
|
||||
} label: {
|
||||
VStack(alignment: .leading) {
|
||||
if(item.Type == "Movie") {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width:100, height: 150)
|
||||
.cornerRadius(10)
|
||||
} else {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width:100, height: 150)
|
||||
.cornerRadius(10).overlay(
|
||||
ZStack {
|
||||
if(item.ItemBadge == 0) {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing
|
||||
)
|
||||
}
|
||||
Text(item.Name)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.ProductionYear))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
ResumeItemGridCell(item: item)
|
||||
}
|
||||
}
|
||||
}.onChange(of: isPortrait) { ip in
|
||||
}.onChange(of: isPortrait) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onAppear(perform: onAppear)
|
||||
if viewModel.isLoading {
|
||||
ActivityIndicator($viewModel.isLoading)
|
||||
}
|
||||
}
|
||||
.onAppear(perform: onAppear)
|
||||
.navigationBarTitle("Search", displayMode: .inline)
|
||||
}
|
||||
}
|
||||
|
||||
struct ResumeItemGridCell: View {
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
|
||||
var item: ResumeItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if item.Type == "Movie" {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
} else {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10).overlay(ZStack {
|
||||
if item.ItemBadge == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing)
|
||||
}
|
||||
Text(item.Name)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.ProductionYear))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,334 +5,187 @@
|
|||
// Created by Aiden Vigue on 5/1/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftyRequest
|
||||
import SwiftyJSON
|
||||
import SDWebImageSwiftUI
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
import SwiftyRequest
|
||||
|
||||
struct LibraryView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@State private var prefill_id: String = "";
|
||||
@State private var library_names: [String: String] = [:]
|
||||
@State private var library_ids: [String] = []
|
||||
@State private var selected_library_id: String = "";
|
||||
@State private var isLoading: Bool = true;
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
@ObservedObject
|
||||
var viewModel: LibraryViewModel
|
||||
|
||||
@State private var viewDidLoad: Bool = false;
|
||||
@State private var filterString: String = "&SortBy=SortName&SortOrder=Descending";
|
||||
@State private var showFiltersPopover: Bool = false;
|
||||
@State private var showSearchPopover: Bool = false;
|
||||
@State private var extraParam: String = "";
|
||||
@State private var title: String = "";
|
||||
@State private var url: String = "";
|
||||
@State private var closeSearch: Bool = false;
|
||||
|
||||
private var itemsPerPage: Int = 100;
|
||||
|
||||
@State private var firstItemIndex: Int = 0;
|
||||
@State private var lastItemIndex: Int = 0;
|
||||
@State private var totalItemCount: Int = 0;
|
||||
|
||||
init(prefill: String?, names: [String: String], libraries: [String]) {
|
||||
_prefill_id = State(wrappedValue: prefill ?? "")
|
||||
_library_names = State(wrappedValue: names)
|
||||
_library_ids = State(wrappedValue: libraries)
|
||||
@State
|
||||
private var viewDidLoad: Bool = false
|
||||
@State
|
||||
private var showFiltersPopover: Bool = false
|
||||
@State
|
||||
private var showSearchPopover: Bool = false
|
||||
@State
|
||||
private var title: String = ""
|
||||
@State
|
||||
private var closeSearch: Bool = false
|
||||
|
||||
init(viewModel: LibraryViewModel, title: String) {
|
||||
self.viewModel = viewModel
|
||||
self._title = State(initialValue: title)
|
||||
}
|
||||
|
||||
init(prefill: String?, names: [String: String], libraries: [String], filter: String) {
|
||||
_prefill_id = State(wrappedValue: prefill ?? "")
|
||||
_library_names = State(wrappedValue: names)
|
||||
_library_ids = State(wrappedValue: libraries)
|
||||
_filterString = State(wrappedValue: filter);
|
||||
}
|
||||
|
||||
init(filter: String, extraParams: String, title: String) {
|
||||
_prefill_id = State(wrappedValue: "erwt");
|
||||
_filterString = State(wrappedValue: filter);
|
||||
_extraParam = State(wrappedValue: extraParams);
|
||||
_title = State(wrappedValue: title)
|
||||
}
|
||||
|
||||
init(extraParams: String, title: String) {
|
||||
_prefill_id = State(wrappedValue: "erwt");
|
||||
_extraParam = State(wrappedValue: extraParams);
|
||||
_title = State(wrappedValue: title)
|
||||
}
|
||||
|
||||
@State var items: [ResumeItem] = []
|
||||
|
||||
func listOnAppear() {
|
||||
if(_viewDidLoad.wrappedValue == false) {
|
||||
//print("running VDL")
|
||||
_viewDidLoad.wrappedValue = true;
|
||||
_library_ids.wrappedValue.append("favorites")
|
||||
_library_names.wrappedValue["favorites"] = "Favorites"
|
||||
|
||||
_library_ids.wrappedValue.append("genres")
|
||||
_library_names.wrappedValue["genres"] = "Genres - WIP"
|
||||
}
|
||||
}
|
||||
|
||||
func loadItems() {
|
||||
recalcTracks()
|
||||
_isLoading.wrappedValue = true;
|
||||
if(_extraParam.wrappedValue == "") {
|
||||
_url.wrappedValue = "/Users/\(globalData.user?.user_id ?? "")/Items?Limit=\(lastItemIndex - firstItemIndex)&StartIndex=\(firstItemIndex)&Recursive=true&Fields=PrimaryImageAspectRatio%2CBasicSyncInfo&ImageTypeLimit=1&EnableImageTypes=Primary%2CBackdrop%2CThumb%2CBanner&IncludeItemTypes=Movie,Series\(selected_library_id == "favorites" ? "&Filters=IsFavorite" : "&ParentId=" + selected_library_id)\(filterString)"
|
||||
} else {
|
||||
_url.wrappedValue = "/Users/\(globalData.user?.user_id ?? "")/Items?Limit=\(lastItemIndex - firstItemIndex)&StartIndex=\(firstItemIndex)&Recursive=true&Fields=PrimaryImageAspectRatio%2CBasicSyncInfo&ImageTypeLimit=1&EnableImageTypes=Primary%2CBackdrop%2CThumb%2CBanner&IncludeItemTypes=Movie,Series\(filterString)\(extraParam)"
|
||||
}
|
||||
|
||||
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + _url.wrappedValue)
|
||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||
request.contentType = "application/json"
|
||||
request.acceptType = "application/json"
|
||||
|
||||
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let body = response.body
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
_totalItemCount.wrappedValue = json["TotalRecordCount"].int ?? 0;
|
||||
for (_,item):(String, JSON) in json["Items"] {
|
||||
// Do something you want
|
||||
let itemObj = ResumeItem()
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
if(itemObj.Type == "Series") {
|
||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = nil
|
||||
itemObj.SeasonId = nil
|
||||
itemObj.SeriesId = nil
|
||||
itemObj.SeriesName = nil
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
} else {
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = item["ParentIndexNumber"].int ?? nil
|
||||
itemObj.SeasonId = item["SeasonId"].string ?? nil
|
||||
itemObj.SeriesId = item["SeriesId"].string ?? nil
|
||||
itemObj.SeriesName = item["SeriesName"].string ?? nil
|
||||
}
|
||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||
_items.wrappedValue.append(itemObj)
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
break
|
||||
case .failure(let error):
|
||||
debugPrint(error)
|
||||
break
|
||||
}
|
||||
_isLoading.wrappedValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func onAppear() {
|
||||
if(_prefill_id.wrappedValue != "") {
|
||||
_selected_library_id.wrappedValue = _prefill_id.wrappedValue;
|
||||
}
|
||||
if(_items.wrappedValue.count == 0) {
|
||||
_firstItemIndex.wrappedValue = 0;
|
||||
_lastItemIndex.wrappedValue = itemsPerPage;
|
||||
loadItems()
|
||||
viewModel.globalData = globalData
|
||||
if viewModel.items.isEmpty {
|
||||
recalcTracks()
|
||||
viewModel.requestInitItems()
|
||||
}
|
||||
}
|
||||
|
||||
func nextPage() {
|
||||
_firstItemIndex.wrappedValue = _lastItemIndex.wrappedValue;
|
||||
_lastItemIndex.wrappedValue = _firstItemIndex.wrappedValue + itemsPerPage;
|
||||
|
||||
if(_lastItemIndex.wrappedValue > _totalItemCount.wrappedValue) {
|
||||
_firstItemIndex.wrappedValue = _totalItemCount.wrappedValue - itemsPerPage;
|
||||
_lastItemIndex.wrappedValue = _totalItemCount.wrappedValue;
|
||||
}
|
||||
|
||||
_items.wrappedValue = [];
|
||||
loadItems()
|
||||
}
|
||||
|
||||
func previousPage() {
|
||||
_lastItemIndex.wrappedValue = _firstItemIndex.wrappedValue;
|
||||
_firstItemIndex.wrappedValue = _lastItemIndex.wrappedValue - itemsPerPage;
|
||||
|
||||
if(_firstItemIndex.wrappedValue < 0) {
|
||||
_firstItemIndex.wrappedValue = 0;
|
||||
_lastItemIndex.wrappedValue = itemsPerPage;
|
||||
}
|
||||
|
||||
_items.wrappedValue = [];
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
@Environment(\.verticalSizeClass)
|
||||
var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass)
|
||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
var isPortrait: Bool {
|
||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
func recalcTracks() {
|
||||
let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125));
|
||||
let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125))
|
||||
_tracks.wrappedValue = []
|
||||
for _ in (0..<trkCnt)
|
||||
{
|
||||
_tracks.wrappedValue.append(GridItem.init(.flexible()))
|
||||
for _ in 0 ..< trkCnt {
|
||||
_tracks.wrappedValue.append(GridItem(.flexible()))
|
||||
}
|
||||
}
|
||||
|
||||
@State private var tracks: [GridItem] = []
|
||||
|
||||
|
||||
@State
|
||||
private var tracks: [GridItem] = []
|
||||
|
||||
var body: some View {
|
||||
if(prefill_id != "") {
|
||||
LoadingView(isShowing: $isLoading) {
|
||||
ScrollView(.vertical) {
|
||||
Spacer().frame(height: 16)
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.Id) { item in
|
||||
NavigationLink(destination: ItemView(item: item )) {
|
||||
VStack(alignment: .leading) {
|
||||
if(item.Type == "Movie") {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width:100, height: 150)
|
||||
.cornerRadius(10)
|
||||
} else {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width:100, height: 150)
|
||||
.cornerRadius(10).overlay(
|
||||
ZStack {
|
||||
if(item.ItemBadge == 0) {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing
|
||||
)
|
||||
}
|
||||
Text(item.Name)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.ProductionYear))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
}
|
||||
LoadingView(isShowing: $viewModel.isLoading) {
|
||||
ScrollView(.vertical) {
|
||||
Spacer().frame(height: 16)
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.Id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
ItemGridView(item: item)
|
||||
}
|
||||
}
|
||||
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
|
||||
recalcTracks()
|
||||
}
|
||||
Spacer().frame(height: 16)
|
||||
}
|
||||
.overrideViewPreference(.unspecified)
|
||||
.onAppear(perform: onAppear)
|
||||
.onChange(of: filterString) { tag in
|
||||
isLoading = true;
|
||||
items = [];
|
||||
firstItemIndex = 0;
|
||||
lastItemIndex = itemsPerPage;
|
||||
loadItems();
|
||||
}
|
||||
.navigationTitle(extraParam == "" ? (library_names[prefill_id] ?? "Library") : title)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
if(totalItemCount > itemsPerPage) {
|
||||
if(firstItemIndex != 0) {
|
||||
Button {
|
||||
previousPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
}
|
||||
}
|
||||
if(lastItemIndex != totalItemCount) {
|
||||
Button {
|
||||
nextPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: LibrarySearchView(url: url, close: $closeSearch), isActive: $closeSearch) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
Button {
|
||||
showFiltersPopover = true
|
||||
} label: {
|
||||
Image(systemName: "line.horizontal.3.decrease")
|
||||
}
|
||||
}
|
||||
}.sheet( isPresented: self.$showFiltersPopover) { LibraryFilterView(library: selected_library_id, output: $filterString, close: $showFiltersPopover).environmentObject(self.globalData) }
|
||||
} else {
|
||||
List(library_ids, id:\.self) { id in
|
||||
if(id != "genres") {
|
||||
NavigationLink(destination: LibraryView(prefill: id, names: library_names, libraries: library_ids)) {
|
||||
Text(library_names[id] ?? "").foregroundColor(Color.primary)
|
||||
}
|
||||
.gesture(DragGesture().onChanged { value in
|
||||
if value.translation.height > 0 {
|
||||
print("Scroll down")
|
||||
} else {
|
||||
NavigationLink(destination: LibraryView(prefill: id, names: library_names, libraries: library_ids)) {
|
||||
Text(library_names[id] ?? "").foregroundColor(Color.primary)
|
||||
print("Scroll up")
|
||||
}
|
||||
})
|
||||
.onChange(of: isPortrait) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
}
|
||||
.overrideViewPreference(.unspecified)
|
||||
.onAppear(perform: onAppear)
|
||||
.navigationTitle(title)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
if viewModel.isHiddenPreviousButton {
|
||||
Button {
|
||||
viewModel.requestPreviousPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
}
|
||||
}
|
||||
}.onAppear(perform: listOnAppear).navigationTitle("All Media")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
NavigationLink(destination: LibrarySearchView(url: "/Users/\(globalData.user?.user_id ?? "")/Items?Limit=300&StartIndex=0&Recursive=true&Fields=PrimaryImageAspectRatio%2CBasicSyncInfo&ImageTypeLimit=1&EnableImageTypes=Primary%2CBackdrop%2CThumb%2CBanner&IncludeItemTypes=Movie,Series\(extraParam)", close: $closeSearch), isActive: $closeSearch) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
if viewModel.isHiddenNextButton {
|
||||
Button {
|
||||
viewModel.requestNextPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: viewModel.filter), close: $closeSearch),
|
||||
isActive: $closeSearch) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
Button {
|
||||
showFiltersPopover = true
|
||||
} label: {
|
||||
Image(systemName: "line.horizontal.3.decrease")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// .sheet(isPresented: self.$showFiltersPopover) {
|
||||
// LibraryFilterView(library: selected_library_id, output: $filterString, close: $showFiltersPopover)
|
||||
// .environmentObject(self.globalData)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
extension LibraryView {
|
||||
struct ItemGridView: View {
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
var item: ResumeItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if item.Type == "Movie" {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
} else {
|
||||
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
|
||||
.resizable()
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10).overlay(ZStack {
|
||||
if item.ItemBadge == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing)
|
||||
}
|
||||
Text(item.Name)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.ProductionYear))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -357,9 +357,9 @@ struct MovieItemView: View {
|
|||
HStack() {
|
||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
Text(genre.Name).font(.footnote)
|
||||
}
|
||||
// }
|
||||
}
|
||||
}.padding(.leading, 16).padding(.trailing,16)
|
||||
}
|
||||
|
@ -371,7 +371,7 @@ struct MovieItemView: View {
|
|||
HStack() {
|
||||
Spacer().frame(width: 16)
|
||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
VStack() {
|
||||
WebImage(url: cast.Image)
|
||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||
|
@ -390,7 +390,7 @@ struct MovieItemView: View {
|
|||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
Spacer().frame(width: 10)
|
||||
}
|
||||
Spacer().frame(width: 16)
|
||||
|
@ -540,9 +540,9 @@ struct MovieItemView: View {
|
|||
HStack() {
|
||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||
ForEach(fullItem.Genres, id: \.Id) {genre in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&Genres=\(genre.Name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")", title: genre.Name)) {
|
||||
Text(genre.Name).font(.footnote)
|
||||
}
|
||||
// }
|
||||
}
|
||||
}.padding(.leading, 16).padding(.trailing,UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||
}
|
||||
|
@ -554,7 +554,7 @@ struct MovieItemView: View {
|
|||
HStack() {
|
||||
Spacer().frame(width: 16)
|
||||
ForEach(fullItem.Cast, id: \.Id) { cast in
|
||||
NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
// NavigationLink(destination: LibraryView(extraParams: "&PersonIds=\(cast.Id)", title: cast.Name)) {
|
||||
VStack() {
|
||||
WebImage(url: cast.Image)
|
||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||
|
@ -573,7 +573,7 @@ struct MovieItemView: View {
|
|||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
Spacer().frame(width: 10)
|
||||
}
|
||||
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
|
||||
|
|
Loading…
Reference in New Issue