diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index b01de757..fa91c00a 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -420,7 +420,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = me.vigue.JellyfinPlayer; + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -443,7 +444,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = me.vigue.JellyfinPlayer; + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/JellyfinPlayer/.DS_Store b/JellyfinPlayer/.DS_Store index aaedabfe..b238c67f 100644 Binary files a/JellyfinPlayer/.DS_Store and b/JellyfinPlayer/.DS_Store differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json index 9221b9bb..6e0452d7 100644 --- a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,41 +1,49 @@ { "images" : [ { + "filename" : "Icon-Spotlight-42.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { + "filename" : "Icon-60.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { + "filename" : "Icon-Small@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { + "filename" : "Icon-Small@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { + "filename" : "Icon-Spotlight-40@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { + "filename" : "Icon-Spotlight-40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { + "filename" : "Icon-60@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { + "filename" : "Icon-60@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" @@ -46,46 +54,55 @@ "size" : "20x20" }, { + "filename" : "Icon-Spotlight-41.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { + "filename" : "Icon-Small.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { + "filename" : "Icon-Small@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { + "filename" : "Icon-Spotlight-40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { + "filename" : "Icon-Spotlight-40@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { + "filename" : "Icon-76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { + "filename" : "Icon-76@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { + "filename" : "Icon-iPadPro@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { + "filename" : "Icon.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60.png new file mode 100644 index 00000000..14d9f2b4 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 00000000..3b229423 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 00000000..7bd4a8e8 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 00000000..e9f490d4 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 00000000..dfe42ead Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 00000000..69ebef93 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png new file mode 100644 index 00000000..e687a70a Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 00000000..e687a70a Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 00000000..bac10d91 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png new file mode 100644 index 00000000..6b59325e Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png new file mode 100644 index 00000000..ca49928d Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png new file mode 100644 index 00000000..ca49928d Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png new file mode 100644 index 00000000..8b8e7cc6 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png new file mode 100644 index 00000000..6b59325e Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png new file mode 100644 index 00000000..6b59325e Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png new file mode 100644 index 00000000..b6cc054f Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png differ diff --git a/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon.png b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon.png new file mode 100644 index 00000000..a8b37858 Binary files /dev/null and b/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/Icon.png differ diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index ffd27d9c..86b8ec29 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -157,8 +157,8 @@ struct ConnectToServerView: View { do { try viewContext.save() print("Saved to Core Data Store") - jsi.did = true _rootIsActive.wrappedValue = false + jsi.did = true } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/JellyfinPlayer/ContentView.swift b/JellyfinPlayer/ContentView.swift index da54b118..e331670e 100644 --- a/JellyfinPlayer/ContentView.swift +++ b/JellyfinPlayer/ContentView.swift @@ -271,71 +271,82 @@ struct ContentView: View { } var body: some View { - LoadingView(isShowing: $isLoading) { - TabView(selection: $tabSelection) { - NavigationView() { - VStack { - NavigationLink(destination: ConnectToServerView(isActive: $needsToSelectServer), isActive: $needsToSelectServer) { - EmptyView() - }.isDetailLink(false) - NavigationLink(destination: ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored), isActive: $isSignInErrored) { - EmptyView() - }.isDetailLink(false) - if(!needsToSelectServer && !isSignInErrored) { - VStack(alignment: .leading) { - ScrollView() { - Spacer().frame(height: self.isPortrait ? 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)) - Spacer() - NavigationLink(destination: LibraryView(prefill: library_id, names: library_names, libraries: libraries, 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)) + if(!jsi.did) { + LoadingView(isShowing: $isLoading) { + TabView(selection: $tabSelection) { + NavigationView() { + VStack { + NavigationLink(destination: ConnectToServerView(isActive: $needsToSelectServer), isActive: $needsToSelectServer) { + EmptyView() + }.isDetailLink(false) + NavigationLink(destination: ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored), isActive: $isSignInErrored) { + EmptyView() + }.isDetailLink(false) + if(!needsToSelectServer && !isSignInErrored) { + VStack(alignment: .leading) { + ScrollView() { + Spacer().frame(height: self.isPortrait ? 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)) + Spacer() + NavigationLink(destination: LibraryView(prefill: library_id, names: library_names, libraries: libraries, 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)) + } + Spacer().frame(height: 7) } - Spacer().frame(height: 7) + } + } + } + .navigationTitle("Home") + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + Button { + print("Settings tapped!") + } label: { + Image(systemName: "gear") } } } } - .navigationTitle("Home") - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button { - print("Settings tapped!") - } label: { - Image(systemName: "gear") - } - } + .tabItem({ + Text("Home") + Image(systemName: "house") + }) + .tag("Home") + NavigationView() { + LibraryView(prefill: "", names: library_names, libraries: libraries) + .navigationTitle("Library") } + .tabItem({ + Text("All Media") + Image(systemName: "folder") + }) + .tag("All Media") + } - .tabItem({ - Text("Home") - Image(systemName: "house") - }) - .tag("Home") - NavigationView() { - LibraryView(prefill: "", names: library_names, libraries: libraries) - .navigationTitle("Library") - } - .tabItem({ - Text("All Media") - Image(systemName: "folder") - }) - .tag("All Media") - + }.environmentObject(globalData) + .onAppear(perform: startup) + .navigationViewStyle(StackNavigationViewStyle()) + .alert(isPresented: $isNetworkErrored) { + Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok"))) } - }.environmentObject(globalData) - .onAppear(perform: startup) - .navigationViewStyle(StackNavigationViewStyle()) - .alert(isPresented: $isNetworkErrored) { - Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok"))) + } else { + Text("Signing in...") + .onAppear(perform: { + DispatchQueue.global(qos: .userInitiated).async { [self] in + print("Signing in") + sleep(3) + jsi.did = false + } + }) } } } diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index 4c9dcc4b..a35abd09 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -81,62 +81,64 @@ struct ContinueWatchingView: View { if(isLoading == false) { Spacer().frame(width:16) ForEach(resumeItems, id: \.Id) { item in - VStack(alignment: .leading) { - Spacer().frame(height: 10) - if(item.Type == "Episode") { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .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() - .scaledToFit() - .cornerRadius(10) - } - .frame(width: 320, height: 180) - .cornerRadius(10) - .overlay( - ZStack { - Text("S\(String(item.ParentIndexNumber ?? 0)):E\(String(item.IndexNumber ?? 0)) - \(item.Name)") - .font(.caption) - .padding(6) - .foregroundColor(.white) - }.background(Color.black) - .opacity(0.8) - .cornerRadius(10.0) - .padding(6), alignment: .topTrailing - ) - .overlay( - RoundedRectangle(cornerRadius: 10, style: .circular) - .fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4)) - .frame(width: CGFloat((item.ItemProgress/100)*320), height: 180) - .padding(0), alignment: .bottomLeading - ) - .shadow(radius: 5) - } else { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .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() - .scaledToFit() - .cornerRadius(10) - } - .frame(width: 320, height: 180) - .cornerRadius(10) - .overlay( - RoundedRectangle(cornerRadius: 10, style: .circular) - .fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4)) - .frame(width: CGFloat((item.ItemProgress/100)*320), height: 180) - .padding(0), alignment: .bottomLeading - ) - .shadow(radius: 5) - } - Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)") - .font(.callout) - .fontWeight(.semibold) - .foregroundColor(.primary) - Spacer().frame(height: 5) - }.padding(.trailing, 5) + NavigationLink(destination: ItemView(item: item)) { + VStack(alignment: .leading) { + Spacer().frame(height: 10) + if(item.Type == "Episode") { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .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() + .scaledToFit() + .cornerRadius(10) + } + .frame(width: 320, height: 180) + .cornerRadius(10) + .overlay( + ZStack { + Text("S\(String(item.ParentIndexNumber ?? 0)):E\(String(item.IndexNumber ?? 0)) - \(item.Name)") + .font(.caption) + .padding(6) + .foregroundColor(.white) + }.background(Color.black) + .opacity(0.8) + .cornerRadius(10.0) + .padding(6), alignment: .topTrailing + ) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .circular) + .fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4)) + .frame(width: CGFloat((item.ItemProgress/100)*320), height: 180) + .padding(0), alignment: .bottomLeading + ) + .shadow(radius: 5) + } else { + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .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() + .scaledToFit() + .cornerRadius(10) + } + .frame(width: 320, height: 180) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .circular) + .fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4)) + .frame(width: CGFloat((item.ItemProgress/100)*320), height: 180) + .padding(0), alignment: .bottomLeading + ) + .shadow(radius: 5) + } + Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)") + .font(.callout) + .fontWeight(.semibold) + .foregroundColor(.primary) + Spacer().frame(height: 5) + }.padding(.trailing, 5) + } } Spacer().frame(width:14) } diff --git a/JellyfinPlayer/Info.plist b/JellyfinPlayer/Info.plist index 1655e670..0ba1f6fa 100644 --- a/JellyfinPlayer/Info.plist +++ b/JellyfinPlayer/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Jellyfin iOS + Jellyfin CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/JellyfinPlayer/JellyfinPlayerApp.swift b/JellyfinPlayer/JellyfinPlayerApp.swift index 49292e08..77cab410 100644 --- a/JellyfinPlayer/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/JellyfinPlayerApp.swift @@ -18,22 +18,13 @@ struct JellyfinPlayerApp: App { var body: some Scene { WindowGroup { - if(!jsi.did) { - ContentView() - .environment(\.managedObjectContext, persistenceController.container.viewContext) - .environmentObject(jsi) - .withHostingWindow() { window in - window?.rootViewController = PreferenceUIHostingController(wrappedView: ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext) - .environmentObject(jsi)) - } - } else { - Text("Please wait...") - .onAppear(perform: { - print("Signing in") - sleep(1) - jsi.did = false - }) - } + ContentView() + .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(jsi) + .withHostingWindow() { window in + window?.rootViewController = PreferenceUIHostingController(wrappedView: ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(jsi)) + } } } } diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index ae932659..0cb0326c 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -87,51 +87,53 @@ struct LatestMediaView: View { HStack() { Spacer().frame(width:18) ForEach(resumeItems, id: \.Id) { item in - VStack(alignment: .leading) { - if(item.Type == "Series") { - Spacer().frame(height:10) - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .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() - .scaledToFit() - .cornerRadius(10) - } - .frame(width: 100, height: 150) - .cornerRadius(10) - .overlay( - ZStack { - Text("\(String(item.ItemBadge ?? 0))") - .font(.caption) - .padding(3) - .foregroundColor(.white) - }.background(Color.black) - .opacity(0.8) - .cornerRadius(10.0) - .padding(3), alignment: .topTrailing - ).shadow(radius: 6) - } else { - Spacer().frame(height:10) - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .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() - .scaledToFit() - .cornerRadius(10) - } - .frame(width: 100, height: 150) - .cornerRadius(10) - .shadow(radius: 6) - } - Text(item.Name) - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.primary) - .lineLimit(1) - Spacer().frame(height:5) - }.frame(width: 100) + NavigationLink(destination: ItemView(item: item)) { + VStack(alignment: .leading) { + if(item.Type == "Series") { + Spacer().frame(height:10) + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .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() + .scaledToFit() + .cornerRadius(10) + } + .frame(width: 100, height: 150) + .cornerRadius(10) + .overlay( + ZStack { + Text("\(String(item.ItemBadge ?? 0))") + .font(.caption) + .padding(3) + .foregroundColor(.white) + }.background(Color.black) + .opacity(0.8) + .cornerRadius(10.0) + .padding(3), alignment: .topTrailing + ).shadow(radius: 6) + } else { + Spacer().frame(height:10) + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .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() + .scaledToFit() + .cornerRadius(10) + } + .frame(width: 100, height: 150) + .cornerRadius(10) + .shadow(radius: 6) + } + Text(item.Name) + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.primary) + .lineLimit(1) + Spacer().frame(height:5) + }.frame(width: 100) + } Spacer().frame(width: 14) } Spacer().frame(width:18) diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 64bfa878..2dd62278 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -32,7 +32,7 @@ struct LibrarySearchView: View { func onAppear() { _isLoading.wrappedValue = true; _items.wrappedValue = []; - let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + _url.wrappedValue + "&searchTerm=" + searchQuery) + let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + _url.wrappedValue + "&searchTerm=" + searchQuery.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) request.headerParameters["X-Emby-Authorization"] = globalData.authHeader request.contentType = "application/json" request.acceptType = "application/json" diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index d9e62712..181e64ba 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -274,9 +274,7 @@ struct LibraryView: View { }.onAppear(perform: listOnAppear).overrideViewPreference(.unspecified).navigationTitle("All Media") .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - Button { - print("Search tapped!") - } label: { + NavigationLink(destination: LibrarySearchView(url: "/Users/\(globalData.user?.user_id ?? "")/Items?Limit=60&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") } } diff --git a/JellyfinPlayer/NextUpView.swift b/JellyfinPlayer/NextUpView.swift index 70e50ae9..56b2d0e0 100644 --- a/JellyfinPlayer/NextUpView.swift +++ b/JellyfinPlayer/NextUpView.swift @@ -75,28 +75,35 @@ struct NextUpView: View { if(isLoading == false) { Spacer().frame(width:18) ForEach(resumeItems, id: \.Id) { item in - VStack(alignment: .leading) { - Spacer().frame(height:10) - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .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() - .scaledToFit() - .cornerRadius(10) - } - .frame(width: 100, height: 150) - .cornerRadius(10) - .shadow(radius: 6) - Text(item.SeriesName ?? "") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.primary) - .lineLimit(1) - Spacer().frame(height:5) + NavigationLink(destination: ItemView(item: item)) { + VStack(alignment: .leading) { + Spacer().frame(height:10) + WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) + .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + .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() + .scaledToFit() + .cornerRadius(10) + } + .frame(width: 100, height: 150) + .cornerRadius(10) + .shadow(radius: 6) + Text(item.SeriesName ?? "") + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.primary) + .lineLimit(1) + Text("S\(String(item.ParentIndexNumber ?? 0))E\(String(item.IndexNumber ?? 0))") + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + Spacer().frame(height:5) + } + .frame(width: 100) + Spacer().frame(width:12) } - .frame(width: 100) - Spacer().frame(width:12) } Spacer().frame(width:18) } diff --git a/JellyfinPlayer/PlayerDemo.swift b/JellyfinPlayer/PlayerDemo.swift index ef695656..e1bc9ae1 100644 --- a/JellyfinPlayer/PlayerDemo.swift +++ b/JellyfinPlayer/PlayerDemo.swift @@ -100,15 +100,13 @@ struct PlayerDemo: View { _inactivity.wrappedValue = true } if((lastPosition == Double(vlcplayer.position) && vlcplayer.state != VLCMediaPlayerState.paused)) { - if(iterations > 3) { + if(iterations > 5) { _iterations.wrappedValue = 0; _streamLoading.wrappedValue = true; - print("Buffering") } _iterations.wrappedValue+=1; } else { _iterations.wrappedValue = 0; - print("Not Buffering") _streamLoading.wrappedValue = false; } if(vlcplayer.state == VLCMediaPlayerState.error) { @@ -204,7 +202,7 @@ struct PlayerDemo: View { request.headerParameters["X-Emby-Authorization"] = globalData.authHeader request.contentType = "application/json" request.acceptType = "application/json" - request.messageBody = "{\"DeviceProfile\":{\"MaxStreamingBitrate\":120000000,\"MaxStaticBitrate\":100000000,\"MusicStreamingTranscodingBitrate\":384000,\"DirectPlayProfiles\":[{\"Container\":\"webm\",\"Type\":\"Video\",\"VideoCodec\":\"vp8,vp9\",\"AudioCodec\":\"vorbis\"},{\"Container\":\"mp4,m4v,mkv\",\"Type\":\"Video\",\"VideoCodec\":\"hevc,h264,vp8,vp9\",\"AudioCodec\":\"aac,mp3,ac3,eac3,flac,alac,vorbis,dts\"},{\"Container\":\"mov\",\"Type\":\"Video\",\"VideoCodec\":\"h264\",\"AudioCodec\":\"aac,mp3,ac3,eac3,flac,alac,vorbis\"},{\"Container\":\"mp3\",\"Type\":\"Audio\"},{\"Container\":\"aac\",\"Type\":\"Audio\"},{\"Container\":\"m4a\",\"AudioCodec\":\"aac\",\"Type\":\"Audio\"},{\"Container\":\"m4b\",\"AudioCodec\":\"aac\",\"Type\":\"Audio\"},{\"Container\":\"flac\",\"Type\":\"Audio\"},{\"Container\":\"alac\",\"Type\":\"Audio\"},{\"Container\":\"m4a\",\"AudioCodec\":\"alac\",\"Type\":\"Audio\"},{\"Container\":\"m4b\",\"AudioCodec\":\"alac\",\"Type\":\"Audio\"},{\"Container\":\"webma\",\"Type\":\"Audio\"},{\"Container\":\"webm\",\"AudioCodec\":\"webma\",\"Type\":\"Audio\"},{\"Container\":\"wav\",\"Type\":\"Audio\"}],\"TranscodingProfiles\":[{\"Container\":\"aac\",\"Type\":\"Audio\",\"AudioCodec\":\"aac\",\"Context\":\"Streaming\",\"Protocol\":\"hls\",\"MaxAudioChannels\":\"6\",\"MinSegments\":\"2\",\"BreakOnNonKeyFrames\":true},{\"Container\":\"aac\",\"Type\":\"Audio\",\"AudioCodec\":\"aac\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"mp3\",\"Type\":\"Audio\",\"AudioCodec\":\"mp3\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"wav\",\"Type\":\"Audio\",\"AudioCodec\":\"wav\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"mp3\",\"Type\":\"Audio\",\"AudioCodec\":\"mp3\",\"Context\":\"Static\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"aac\",\"Type\":\"Audio\",\"AudioCodec\":\"aac\",\"Context\":\"Static\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"wav\",\"Type\":\"Audio\",\"AudioCodec\":\"wav\",\"Context\":\"Static\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"ts\",\"Type\":\"Video\",\"AudioCodec\":\"aac,mp3,ac3,eac3\",\"VideoCodec\":\"h264\",\"Context\":\"Streaming\",\"Protocol\":\"hls\",\"MaxAudioChannels\":\"6\",\"MinSegments\":\"2\",\"BreakOnNonKeyFrames\":true},{\"Container\":\"webm\",\"Type\":\"Video\",\"AudioCodec\":\"vorbis\",\"VideoCodec\":\"vpx\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"mp4\",\"Type\":\"Video\",\"AudioCodec\":\"aac,mp3,ac3,eac3,flac,alac,vorbis\",\"VideoCodec\":\"h264\",\"Context\":\"Static\",\"Protocol\":\"http\"}],\"ContainerProfiles\":[],\"CodecProfiles\":[{\"Type\":\"Video\",\"Codec\":\"h264\",\"Conditions\":[{\"Condition\":\"NotEquals\",\"Property\":\"IsAnamorphic\",\"Value\":\"true\",\"IsRequired\":false},{\"Condition\":\"EqualsAny\",\"Property\":\"VideoProfile\",\"Value\":\"high|main|baseline|constrained baseline\",\"IsRequired\":false},{\"Condition\":\"LessThanEqual\",\"Property\":\"VideoLevel\",\"Value\":\"80\",\"IsRequired\":false},{\"Condition\":\"NotEquals\",\"Property\":\"IsInterlaced\",\"Value\":\"true\",\"IsRequired\":false}]},{\"Type\":\"Video\",\"Codec\":\"hevc\",\"Conditions\":[{\"Condition\":\"NotEquals\",\"Property\":\"IsAnamorphic\",\"Value\":\"true\",\"IsRequired\":false},{\"Condition\":\"EqualsAny\",\"Property\":\"VideoProfile\",\"Value\":\"main|main 10\",\"IsRequired\":false},{\"Condition\":\"LessThanEqual\",\"Property\":\"VideoLevel\",\"Value\":\"190\",\"IsRequired\":false},{\"Condition\":\"NotEquals\",\"Property\":\"IsInterlaced\",\"Value\":\"true\",\"IsRequired\":false}]}],\"SubtitleProfiles\":[{\"Format\":\"vtt\",\"Method\":\"External\"},{\"Format\":\"ass\",\"Method\":\"External\"},{\"Format\":\"ssa\",\"Method\":\"External\"}],\"ResponseProfiles\":[{\"Type\":\"Video\",\"Container\":\"m4v\",\"MimeType\":\"video/mp4\"}]}}".data(using: .ascii); + request.messageBody = "{\"DeviceProfile\":{\"MaxStreamingBitrate\":70000000,\"MaxStaticBitrate\":150000000,\"MusicStreamingTranscodingBitrate\":384000,\"DirectPlayProfiles\":[{\"Container\":\"webm\",\"Type\":\"Video\",\"VideoCodec\":\"vp8,vp9\",\"AudioCodec\":\"vorbis\"},{\"Container\":\"mp4,m4v,mkv\",\"Type\":\"Video\",\"VideoCodec\":\"hevc,h264,vp8,vp9\",\"AudioCodec\":\"aac,mp3,ac3,eac3,flac,alac,vorbis,dts\"},{\"Container\":\"mov\",\"Type\":\"Video\",\"VideoCodec\":\"h264\",\"AudioCodec\":\"aac,mp3,ac3,eac3,flac,alac,vorbis\"},{\"Container\":\"mp3\",\"Type\":\"Audio\"},{\"Container\":\"aac\",\"Type\":\"Audio\"},{\"Container\":\"m4a\",\"AudioCodec\":\"aac\",\"Type\":\"Audio\"},{\"Container\":\"m4b\",\"AudioCodec\":\"aac\",\"Type\":\"Audio\"},{\"Container\":\"flac\",\"Type\":\"Audio\"},{\"Container\":\"alac\",\"Type\":\"Audio\"},{\"Container\":\"m4a\",\"AudioCodec\":\"alac\",\"Type\":\"Audio\"},{\"Container\":\"m4b\",\"AudioCodec\":\"alac\",\"Type\":\"Audio\"},{\"Container\":\"webma\",\"Type\":\"Audio\"},{\"Container\":\"webm\",\"AudioCodec\":\"webma\",\"Type\":\"Audio\"},{\"Container\":\"wav\",\"Type\":\"Audio\"}],\"TranscodingProfiles\":[{\"Container\":\"aac\",\"Type\":\"Audio\",\"AudioCodec\":\"aac\",\"Context\":\"Streaming\",\"Protocol\":\"hls\",\"MaxAudioChannels\":\"6\",\"MinSegments\":\"2\",\"BreakOnNonKeyFrames\":true},{\"Container\":\"aac\",\"Type\":\"Audio\",\"AudioCodec\":\"aac\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"mp3\",\"Type\":\"Audio\",\"AudioCodec\":\"mp3\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"wav\",\"Type\":\"Audio\",\"AudioCodec\":\"wav\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"mp3\",\"Type\":\"Audio\",\"AudioCodec\":\"mp3\",\"Context\":\"Static\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"aac\",\"Type\":\"Audio\",\"AudioCodec\":\"aac\",\"Context\":\"Static\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"wav\",\"Type\":\"Audio\",\"AudioCodec\":\"wav\",\"Context\":\"Static\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"ts\",\"Type\":\"Video\",\"AudioCodec\":\"aac,mp3,ac3,eac3\",\"VideoCodec\":\"h264\",\"Context\":\"Streaming\",\"Protocol\":\"hls\",\"MaxAudioChannels\":\"6\",\"MinSegments\":\"2\",\"BreakOnNonKeyFrames\":true},{\"Container\":\"webm\",\"Type\":\"Video\",\"AudioCodec\":\"vorbis\",\"VideoCodec\":\"vpx\",\"Context\":\"Streaming\",\"Protocol\":\"http\",\"MaxAudioChannels\":\"6\"},{\"Container\":\"mp4\",\"Type\":\"Video\",\"AudioCodec\":\"aac,mp3,ac3,eac3,flac,alac,vorbis\",\"VideoCodec\":\"h264\",\"Context\":\"Static\",\"Protocol\":\"http\"}],\"ContainerProfiles\":[],\"CodecProfiles\":[{\"Type\":\"Video\",\"Codec\":\"h264\",\"Conditions\":[{\"Condition\":\"NotEquals\",\"Property\":\"IsAnamorphic\",\"Value\":\"true\",\"IsRequired\":false},{\"Condition\":\"EqualsAny\",\"Property\":\"VideoProfile\",\"Value\":\"high|main|baseline|constrained baseline\",\"IsRequired\":false},{\"Condition\":\"LessThanEqual\",\"Property\":\"VideoLevel\",\"Value\":\"80\",\"IsRequired\":false},{\"Condition\":\"NotEquals\",\"Property\":\"IsInterlaced\",\"Value\":\"true\",\"IsRequired\":false}]},{\"Type\":\"Video\",\"Codec\":\"hevc\",\"Conditions\":[{\"Condition\":\"NotEquals\",\"Property\":\"IsAnamorphic\",\"Value\":\"true\",\"IsRequired\":false},{\"Condition\":\"EqualsAny\",\"Property\":\"VideoProfile\",\"Value\":\"main|main 10\",\"IsRequired\":false},{\"Condition\":\"LessThanEqual\",\"Property\":\"VideoLevel\",\"Value\":\"190\",\"IsRequired\":false},{\"Condition\":\"NotEquals\",\"Property\":\"IsInterlaced\",\"Value\":\"true\",\"IsRequired\":false}]}],\"SubtitleProfiles\":[{\"Format\":\"vtt\",\"Method\":\"External\"},{\"Format\":\"ass\",\"Method\":\"External\"},{\"Format\":\"ssa\",\"Method\":\"External\"},{\"Format\":\"pgssub\",\"Method\":\"Embed\"},{\"Format\":\"pgs\",\"Method\":\"Embed\"},{\"Format\":\"sub\",\"Method\":\"Embed\"}],\"ResponseProfiles\":[{\"Type\":\"Video\",\"Container\":\"m4v\",\"MimeType\":\"video/mp4\"}]}}".data(using: .ascii); request.responseData() { (result: Result, RestError>) in switch result {