add LibraryListViewModel

This commit is contained in:
PangMo5 2021-05-28 12:20:49 +09:00
parent ed224da0a4
commit 4dae6bc00e
4 changed files with 178 additions and 137 deletions

View File

@ -45,6 +45,7 @@
6213388E265F777C00A81A2A /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388D265F777C00A81A2A /* LibraryViewModel.swift */; };
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* LibraryListView.swift */; };
621338932660107500A81A2A /* String++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* String++.swift */; };
62133895266096EF00A81A2A /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62133894266096EF00A81A2A /* LibraryListViewModel.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 */; };
@ -112,6 +113,7 @@
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>"; };
621338922660107500A81A2A /* String++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String++.swift"; sourceTree = "<group>"; };
62133894266096EF00A81A2A /* LibraryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListViewModel.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>"; };
@ -224,6 +226,7 @@
isa = PBXGroup;
children = (
6213388D265F777C00A81A2A /* LibraryViewModel.swift */,
62133894266096EF00A81A2A /* LibraryListViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -417,6 +420,7 @@
53E4E649263F725B00F67C6B /* MultiSelector.swift in Sources */,
53E4E647263F6CF100F67C6B /* LibraryFilterView.swift in Sources */,
6213388E265F777C00A81A2A /* LibraryViewModel.swift in Sources */,
62133895266096EF00A81A2A /* LibraryListViewModel.swift in Sources */,
6273DD48265F41B3009C1D0B /* JellyfinAPI.swift in Sources */,
53892777263CBB000035E14B /* JellyApiTypings.swift in Sources */,
5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */,

View File

@ -8,78 +8,98 @@
import SwiftUI
import KeychainSwift
import SwiftyRequest
import SwiftyJSON
import Sentry
import SDWebImageSwiftUI
import Sentry
import SwiftyJSON
import SwiftyRequest
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var orientationInfo: OrientationInfo
@StateObject private var globalData = GlobalData()
@EnvironmentObject var jsi: justSignedIn
@Environment(\.managedObjectContext)
private var viewContext
@EnvironmentObject
var orientationInfo: OrientationInfo
@StateObject
private var globalData = GlobalData()
@EnvironmentObject
var jsi: justSignedIn
@FetchRequest(entity: Server.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Server.name, ascending: true)]) private var servers: FetchedResults<Server>
@FetchRequest(entity: SignedInUser.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \SignedInUser.username, ascending: true)]) private var savedUsers: FetchedResults<SignedInUser>
@State private var needsToSelectServer = false;
@State private var isSignInErrored = false;
@State private var isNetworkErrored = false;
@State private var isLoading = false;
@State private var tabSelection: String = "Home";
@State private var libraries: [String] = [];
@State private var library_names: [String: String] = [:];
@State private var librariesShowRecentlyAdded: [String] = [];
@State private var libraryPrefillID: String = "";
@State private var showSettingsPopover: Bool = false;
@State private var viewDidLoad: Bool = false;
@FetchRequest(entity: Server.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Server.name, ascending: true)])
private var servers: FetchedResults<Server>
@FetchRequest(entity: SignedInUser.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \SignedInUser.username,
ascending: true)])
private var savedUsers: FetchedResults<SignedInUser>
@State
private var needsToSelectServer = false
@State
private var isSignInErrored = false
@State
private var isNetworkErrored = false
@State
private var isLoading = false
@State
private var tabSelection: String = "Home"
@State
private var libraries: [String] = []
@State
private var library_names: [String: String] = [:]
@State
private var librariesShowRecentlyAdded: [String] = []
@State
private var libraryPrefillID: String = ""
@State
private var showSettingsPopover: Bool = false
@State
private var viewDidLoad: Bool = false
func startup() {
let size = UIScreen.main.bounds.size
if size.width < size.height {
orientationInfo.orientation = .portrait;
orientationInfo.orientation = .portrait
} else {
orientationInfo.orientation = .landscape;
orientationInfo.orientation = .landscape
}
if(_viewDidLoad.wrappedValue) {
if _viewDidLoad.wrappedValue {
return
}
_viewDidLoad.wrappedValue = true;
_viewDidLoad.wrappedValue = true
SentrySDK.start { options in
options.dsn = "https://75ac77d6af4d406eb989f3d8ef0f119f@o513670.ingest.sentry.io/5778242"
options.debug = false // Enabled debug when first installing is always helpful
options.tracesSampleRate = 1.0
options.releaseName = "ios-" + (Bundle.main.infoDictionary?["CFBundleVersion"] as! String);
options.releaseName = "ios-" + (Bundle.main.infoDictionary?["CFBundleVersion"] as! String)
options.enableOutOfMemoryTracking = true
}
let cache = SDImageCache(namespace: "tiny")
cache.config.maxMemoryCost = 125 * 1024 * 1024 // 125MB memory
cache.config.maxDiskSize = 1000 * 1024 * 1024 // 1000MB disk
SDImageCachesManager.shared.addCache(cache)
SDWebImageManager.defaultImageCache = SDImageCachesManager.shared
_libraries.wrappedValue = []
_library_names.wrappedValue = [:]
_librariesShowRecentlyAdded.wrappedValue = []
if(servers.isEmpty) {
_isLoading.wrappedValue = false;
_needsToSelectServer.wrappedValue = true;
if servers.isEmpty {
_isLoading.wrappedValue = false
_needsToSelectServer.wrappedValue = true
} else {
_isLoading.wrappedValue = true;
let savedUser = savedUsers[0];
_isLoading.wrappedValue = true
let savedUser = savedUsers[0]
let keychain = KeychainSwift();
if(keychain.get("AccessToken_\(savedUser.user_id ?? "")") != nil) {
let keychain = KeychainSwift()
if keychain.get("AccessToken_\(savedUser.user_id ?? "")") != nil {
_globalData.wrappedValue.authToken = keychain.get("AccessToken_\(savedUser.user_id ?? "")") ?? ""
_globalData.wrappedValue.server = servers[0]
_globalData.wrappedValue.user = savedUser
}
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String;
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
var header = "MediaBrowser "
header.append("Client=\"SwiftFin\",")
header.append("Device=\"\(UIDevice.current.name.removeRegexMatches(pattern: "[^\\w\\s]"))\",")
@ -87,48 +107,50 @@ struct ContentView: View {
header.append("Version=\"\(appVersion ?? "0.0.1")\",")
header.append("Token=\"\(globalData.authToken)\"")
globalData.authHeader = header
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + "/Users/Me")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
request.acceptType = "application/json"
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
request.responseData { (result: Result<RestResponse<Data>, RestError>) in
switch result {
case .success( let resp):
case let .success(resp):
do {
let json = try JSON(data: resp.body)
let array2 = json["Configuration"]["LatestItemsExcludes"].arrayObject as? [String] ?? []
let request2 = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + "/Users/\(globalData.user?.user_id ?? "")/Views")
let request2 = RestRequest(method: .get,
url: (globalData.server?.baseURI ?? "") +
"/Users/\(globalData.user?.user_id ?? "")/Views")
request2.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request2.contentType = "application/json"
request2.acceptType = "application/json"
request2.responseData() { (result2: Result<RestResponse<Data>, RestError>) in
request2.responseData { (result2: Result<RestResponse<Data>, RestError>) in
switch result2 {
case .success( let resp):
case let .success(resp):
do {
let json2 = try JSON(data: resp.body)
for (_,item2):(String, JSON) in json2["Items"] {
for (_, item2): (String, JSON) in json2["Items"] {
_library_names.wrappedValue[item2["Id"].string ?? ""] = item2["Name"].string ?? ""
}
for (_,item2):(String, JSON) in json2["Items"] {
if(item2["CollectionType"].string == "tvshows" || item2["CollectionType"].string == "movies") {
for (_, item2): (String, JSON) in json2["Items"] {
if item2["CollectionType"].string == "tvshows" || item2["CollectionType"].string == "movies" {
_libraries.wrappedValue.append(item2["Id"].string ?? "")
_librariesShowRecentlyAdded.wrappedValue.append(item2["Id"].string ?? "")
}
}
_librariesShowRecentlyAdded.wrappedValue = _libraries.wrappedValue.filter { element in
return !array2.contains(element)
!array2.contains(element)
}
_libraries.wrappedValue.forEach { library in
if(_library_names.wrappedValue[library] == nil) {
if _library_names.wrappedValue[library] == nil {
_libraries.wrappedValue.removeAll { ele in
if(library == ele) {
if library == ele {
return true
} else {
return false
@ -136,39 +158,32 @@ struct ContentView: View {
}
}
}
dump(_libraries.wrappedValue)
dump(_librariesShowRecentlyAdded.wrappedValue)
dump(_library_names.wrappedValue)
} catch {
}
break
case .failure(let error):
} catch {}
case let .failure(error):
SentrySDK.capture(error: error)
break
}
let defaults = UserDefaults.standard;
if(defaults.integer(forKey: "InNetworkBandwidth") == 0) {
defaults.setValue(40000000, forKey: "InNetworkBandwidth")
let defaults = UserDefaults.standard
if defaults.integer(forKey: "InNetworkBandwidth") == 0 {
defaults.setValue(40_000_000, forKey: "InNetworkBandwidth")
}
if(defaults.integer(forKey: "OutOfNetworkBandwidth") == 0) {
defaults.setValue(40000000, forKey: "OutOfNetworkBandwidth")
if defaults.integer(forKey: "OutOfNetworkBandwidth") == 0 {
defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth")
}
_isLoading.wrappedValue = false;
_isLoading.wrappedValue = false
}
} catch {
}
break
case .failure( let error):
if(error.response?.status.code == 401) {
_isLoading.wrappedValue = false;
_isSignInErrored.wrappedValue = true;
} catch {}
case let .failure(error):
if error.response?.status.code == 401 {
_isLoading.wrappedValue = false
_isSignInErrored.wrappedValue = true
} else {
SentrySDK.capture(error: error)
_isLoading.wrappedValue = false;
_isNetworkErrored.wrappedValue = true;
_isLoading.wrappedValue = false
_isNetworkErrored.wrappedValue = true
}
}
}
@ -176,34 +191,37 @@ struct ContentView: View {
}
var body: some View {
if(needsToSelectServer) {
NavigationView() {
if needsToSelectServer {
NavigationView {
ConnectToServerView(isActive: $needsToSelectServer)
}
.navigationViewStyle(StackNavigationViewStyle())
.environmentObject(globalData)
} else if(isSignInErrored) {
NavigationView() {
ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored)
} else if isSignInErrored {
NavigationView {
ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server,
reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored)
}
.navigationViewStyle(StackNavigationViewStyle())
.environmentObject(globalData)
} else {
if(!jsi.did) {
if !jsi.did {
LoadingView(isShowing: $isLoading) {
TabView(selection: $tabSelection) {
NavigationView() {
NavigationView {
VStack(alignment: .leading) {
ScrollView() {
ScrollView {
Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 15)
ContinueWatchingView()
NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
VStack(alignment: .leading) {
HStack() {
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
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(viewModel: .init(filter: Filter(parentID: library_id)), title: library_names[library_id] ?? "")) {
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(parentID: library_id)),
title: library_names[library_id] ?? "")) {
Text("See All").font(.subheadline).fontWeight(.bold)
}
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
@ -216,28 +234,31 @@ struct ContentView: View {
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
showSettingsPopover = true;
showSettingsPopover = true
} label: {
Image(systemName: "gear")
}
}
}.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) }
}
.fullScreenCover(isPresented: $showSettingsPopover) {
SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover)
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem({
.tabItem {
Text("Home")
Image(systemName: "house")
})
}
.tag("Home")
NavigationView {
LibraryListView(libraryNames: library_names, libraryIDs: libraries)
LibraryListView(viewModel: .init(libraryNames: library_names, libraryIDs: libraries))
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem({
.tabItem {
Text("All Media")
Image(systemName: "folder")
})
}
.tag("All Media")
}
}
@ -248,13 +269,13 @@ struct ContentView: View {
}
} else {
Text("Signing in...")
.onAppear(perform: {
DispatchQueue.main.async { [self] in
_viewDidLoad.wrappedValue = false
usleep(500000);
self.jsi.did = false;
}
})
.onAppear(perform: {
DispatchQueue.main.async { [self] in
_viewDidLoad.wrappedValue = false
usleep(500_000)
self.jsi.did = false
}
})
}
}
}

View File

@ -0,0 +1,38 @@
//
// LibraryListViewModel.swift
// JellyfinPlayer
//
// Created by PangMo5 on 2021/05/28.
//
import Combine
import CombineMoya
import Foundation
import Moya
import SwiftyJSON
final class LibraryListViewModel: ObservableObject {
fileprivate var provider =
MoyaProvider<JellyfinAPI>(plugins: [NetworkLoggerPlugin()])
@Published
var libraryIDs = [String]()
@Published
var libraryNames = [String: String]()
fileprivate var cancellables = Set<AnyCancellable>()
init(libraryNames: [String: String], libraryIDs: [String]) {
self.libraryIDs = libraryIDs
self.libraryNames = libraryNames
refresh()
}
func refresh() {
libraryIDs.append("favorites")
libraryNames["favorites"] = "Favorites"
libraryIDs.append("genres")
libraryNames["genres"] = "Genres - WIP"
}
}

View File

@ -13,48 +13,26 @@ struct LibraryListView: View {
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"
}
}
@ObservedObject
var viewModel: LibraryListViewModel
var body: some View {
List(libraryIDs, id: \.self) { id in
List(viewModel.libraryIDs, id: \.self) { id in
switch id {
case "favorites":
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(filterTypes: [.isFavorite])),
title: libraryNames[id] ?? "")) {
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
title: viewModel.libraryNames[id] ?? "")) {
Text(viewModel.libraryNames[id] ?? "").foregroundColor(Color.primary)
}
case "genres":
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
Text(viewModel.libraryNames[id] ?? "").foregroundColor(Color.primary)
default:
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(parentID: id)), title: libraryNames[id] ?? "")) {
Text(libraryNames[id] ?? "").foregroundColor(Color.primary)
NavigationLink(destination: LibraryView(viewModel: .init(filter: Filter(parentID: id)),
title: viewModel.libraryNames[id] ?? "")) {
Text(viewModel.libraryNames[id] ?? "").foregroundColor(Color.primary)
}
}
}
.onAppear(perform: listOnAppear)
.navigationTitle("All Media")
.navigationBarItems(trailing:
NavigationLink(destination: LibrarySearchView(viewModel: .init(filter: .init()))) {