Add deeplinking from homepage

This commit is contained in:
Aiden Vigue 2021-05-20 10:48:02 -04:00
parent fc0d08a9fd
commit 5e5e9f7b05
30 changed files with 236 additions and 208 deletions

View File

@ -420,7 +420,8 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.JellyfinPlayer; MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -443,7 +444,8 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.JellyfinPlayer; MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";

Binary file not shown.

View File

@ -1,41 +1,49 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "Icon-Spotlight-42.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "2x", "scale" : "2x",
"size" : "20x20" "size" : "20x20"
}, },
{ {
"filename" : "Icon-60.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "3x", "scale" : "3x",
"size" : "20x20" "size" : "20x20"
}, },
{ {
"filename" : "Icon-Small@2x.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "2x", "scale" : "2x",
"size" : "29x29" "size" : "29x29"
}, },
{ {
"filename" : "Icon-Small@3x.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "3x", "scale" : "3x",
"size" : "29x29" "size" : "29x29"
}, },
{ {
"filename" : "Icon-Spotlight-40@2x.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "2x", "scale" : "2x",
"size" : "40x40" "size" : "40x40"
}, },
{ {
"filename" : "Icon-Spotlight-40@3x.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "3x", "scale" : "3x",
"size" : "40x40" "size" : "40x40"
}, },
{ {
"filename" : "Icon-60@2x.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "2x", "scale" : "2x",
"size" : "60x60" "size" : "60x60"
}, },
{ {
"filename" : "Icon-60@3x.png",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "3x", "scale" : "3x",
"size" : "60x60" "size" : "60x60"
@ -46,46 +54,55 @@
"size" : "20x20" "size" : "20x20"
}, },
{ {
"filename" : "Icon-Spotlight-41.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "20x20" "size" : "20x20"
}, },
{ {
"filename" : "Icon-Small.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "1x", "scale" : "1x",
"size" : "29x29" "size" : "29x29"
}, },
{ {
"filename" : "Icon-Small@2x-1.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "29x29" "size" : "29x29"
}, },
{ {
"filename" : "Icon-Spotlight-40.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "1x", "scale" : "1x",
"size" : "40x40" "size" : "40x40"
}, },
{ {
"filename" : "Icon-Spotlight-40@2x-1.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "40x40" "size" : "40x40"
}, },
{ {
"filename" : "Icon-76.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "1x", "scale" : "1x",
"size" : "76x76" "size" : "76x76"
}, },
{ {
"filename" : "Icon-76@2x.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "76x76" "size" : "76x76"
}, },
{ {
"filename" : "Icon-iPadPro@2x.png",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "2x", "scale" : "2x",
"size" : "83.5x83.5" "size" : "83.5x83.5"
}, },
{ {
"filename" : "Icon.png",
"idiom" : "ios-marketing", "idiom" : "ios-marketing",
"scale" : "1x", "scale" : "1x",
"size" : "1024x1024" "size" : "1024x1024"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View File

@ -157,8 +157,8 @@ struct ConnectToServerView: View {
do { do {
try viewContext.save() try viewContext.save()
print("Saved to Core Data Store") print("Saved to Core Data Store")
jsi.did = true
_rootIsActive.wrappedValue = false _rootIsActive.wrappedValue = false
jsi.did = true
} catch { } catch {
// Replace this implementation with code to handle the error appropriately. // 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. // 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.

View File

@ -271,71 +271,82 @@ struct ContentView: View {
} }
var body: some View { var body: some View {
LoadingView(isShowing: $isLoading) { if(!jsi.did) {
TabView(selection: $tabSelection) { LoadingView(isShowing: $isLoading) {
NavigationView() { TabView(selection: $tabSelection) {
VStack { NavigationView() {
NavigationLink(destination: ConnectToServerView(isActive: $needsToSelectServer), isActive: $needsToSelectServer) { VStack {
EmptyView() NavigationLink(destination: ConnectToServerView(isActive: $needsToSelectServer), isActive: $needsToSelectServer) {
}.isDetailLink(false) EmptyView()
NavigationLink(destination: ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored), isActive: $isSignInErrored) { }.isDetailLink(false)
EmptyView() NavigationLink(destination: ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored), isActive: $isSignInErrored) {
}.isDetailLink(false) EmptyView()
if(!needsToSelectServer && !isSignInErrored) { }.isDetailLink(false)
VStack(alignment: .leading) { if(!needsToSelectServer && !isSignInErrored) {
ScrollView() { VStack(alignment: .leading) {
Spacer().frame(height: self.isPortrait ? 0 : 15) ScrollView() {
ContinueWatchingView() Spacer().frame(height: self.isPortrait ? 0 : 15)
NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) ContinueWatchingView()
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
VStack(alignment: .leading) { ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
HStack() { VStack(alignment: .leading) {
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) HStack() {
Spacer() Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
NavigationLink(destination: LibraryView(prefill: library_id, names: library_names, libraries: libraries, filter: "&SortBy=DateCreated&SortOrder=Descending")) { Spacer()
Text("See All").font(.subheadline).fontWeight(.bold) 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: 0, leading: 16, bottom: 0, trailing: 16))
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) 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") .tabItem({
.toolbar { Text("Home")
ToolbarItemGroup(placement: .navigationBarTrailing) { Image(systemName: "house")
Button { })
print("Settings tapped!") .tag("Home")
} label: { NavigationView() {
Image(systemName: "gear") LibraryView(prefill: "", names: library_names, libraries: libraries)
} .navigationTitle("Library")
}
} }
.tabItem({
Text("All Media")
Image(systemName: "folder")
})
.tag("All Media")
} }
.tabItem({ }.environmentObject(globalData)
Text("Home") .onAppear(perform: startup)
Image(systemName: "house") .navigationViewStyle(StackNavigationViewStyle())
}) .alert(isPresented: $isNetworkErrored) {
.tag("Home") Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok")))
NavigationView() {
LibraryView(prefill: "", names: library_names, libraries: libraries)
.navigationTitle("Library")
}
.tabItem({
Text("All Media")
Image(systemName: "folder")
})
.tag("All Media")
} }
}.environmentObject(globalData) } else {
.onAppear(perform: startup) Text("Signing in...")
.navigationViewStyle(StackNavigationViewStyle()) .onAppear(perform: {
.alert(isPresented: $isNetworkErrored) { DispatchQueue.global(qos: .userInitiated).async { [self] in
Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok"))) print("Signing in")
sleep(3)
jsi.did = false
}
})
} }
} }
} }

View File

@ -81,62 +81,64 @@ struct ContinueWatchingView: View {
if(isLoading == false) { if(isLoading == false) {
Spacer().frame(width:16) Spacer().frame(width:16)
ForEach(resumeItems, id: \.Id) { item in ForEach(resumeItems, id: \.Id) { item in
VStack(alignment: .leading) { NavigationLink(destination: ItemView(item: item)) {
Spacer().frame(height: 10) VStack(alignment: .leading) {
if(item.Type == "Episode") { Spacer().frame(height: 10)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!) if(item.Type == "Episode") {
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!)
.placeholder { .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .placeholder {
.resizable() Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
.scaledToFit() .resizable()
.cornerRadius(10) .scaledToFit()
} .cornerRadius(10)
.frame(width: 320, height: 180) }
.cornerRadius(10) .frame(width: 320, height: 180)
.overlay( .cornerRadius(10)
ZStack { .overlay(
Text("S\(String(item.ParentIndexNumber ?? 0)):E\(String(item.IndexNumber ?? 0)) - \(item.Name)") ZStack {
.font(.caption) Text("S\(String(item.ParentIndexNumber ?? 0)):E\(String(item.IndexNumber ?? 0)) - \(item.Name)")
.padding(6) .font(.caption)
.foregroundColor(.white) .padding(6)
}.background(Color.black) .foregroundColor(.white)
.opacity(0.8) }.background(Color.black)
.cornerRadius(10.0) .opacity(0.8)
.padding(6), alignment: .topTrailing .cornerRadius(10.0)
) .padding(6), alignment: .topTrailing
.overlay( )
RoundedRectangle(cornerRadius: 10, style: .circular) .overlay(
.fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4)) RoundedRectangle(cornerRadius: 10, style: .circular)
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 180) .fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4))
.padding(0), alignment: .bottomLeading .frame(width: CGFloat((item.ItemProgress/100)*320), height: 180)
) .padding(0), alignment: .bottomLeading
.shadow(radius: 5) )
} else { .shadow(radius: 5)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!) } else {
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=560&fillHeight=315&quality=90&tag=\(item.Image)")!)
.placeholder { .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .placeholder {
.resizable() Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
.scaledToFit() .resizable()
.cornerRadius(10) .scaledToFit()
} .cornerRadius(10)
.frame(width: 320, height: 180) }
.cornerRadius(10) .frame(width: 320, height: 180)
.overlay( .cornerRadius(10)
RoundedRectangle(cornerRadius: 10, style: .circular) .overlay(
.fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4)) RoundedRectangle(cornerRadius: 10, style: .circular)
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 180) .fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4))
.padding(0), alignment: .bottomLeading .frame(width: CGFloat((item.ItemProgress/100)*320), height: 180)
) .padding(0), alignment: .bottomLeading
.shadow(radius: 5) )
} .shadow(radius: 5)
Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)") }
.font(.callout) Text("\(item.Type == "Episode" ? item.SeriesName ?? "" : item.Name)")
.fontWeight(.semibold) .font(.callout)
.foregroundColor(.primary) .fontWeight(.semibold)
Spacer().frame(height: 5) .foregroundColor(.primary)
}.padding(.trailing, 5) Spacer().frame(height: 5)
}.padding(.trailing, 5)
}
} }
Spacer().frame(width:14) Spacer().frame(width:14)
} }

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Jellyfin iOS</string> <string>Jellyfin</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@ -18,22 +18,13 @@ struct JellyfinPlayerApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
if(!jsi.did) { ContentView()
ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(jsi)
.environmentObject(jsi) .withHostingWindow() { window in
.withHostingWindow() { window in window?.rootViewController = PreferenceUIHostingController(wrappedView: ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext)
window?.rootViewController = PreferenceUIHostingController(wrappedView: ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(jsi))
.environmentObject(jsi)) }
}
} else {
Text("Please wait...")
.onAppear(perform: {
print("Signing in")
sleep(1)
jsi.did = false
})
}
} }
} }
} }

View File

@ -87,51 +87,53 @@ struct LatestMediaView: View {
HStack() { HStack() {
Spacer().frame(width:18) Spacer().frame(width:18)
ForEach(resumeItems, id: \.Id) { item in ForEach(resumeItems, id: \.Id) { item in
VStack(alignment: .leading) { NavigationLink(destination: ItemView(item: item)) {
if(item.Type == "Series") { VStack(alignment: .leading) {
Spacer().frame(height:10) if(item.Type == "Series") {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) Spacer().frame(height:10)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!)
.placeholder { .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .placeholder {
.resizable() Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
.scaledToFit() .resizable()
.cornerRadius(10) .scaledToFit()
} .cornerRadius(10)
.frame(width: 100, height: 150) }
.cornerRadius(10) .frame(width: 100, height: 150)
.overlay( .cornerRadius(10)
ZStack { .overlay(
Text("\(String(item.ItemBadge ?? 0))") ZStack {
.font(.caption) Text("\(String(item.ItemBadge ?? 0))")
.padding(3) .font(.caption)
.foregroundColor(.white) .padding(3)
}.background(Color.black) .foregroundColor(.white)
.opacity(0.8) }.background(Color.black)
.cornerRadius(10.0) .opacity(0.8)
.padding(3), alignment: .topTrailing .cornerRadius(10.0)
).shadow(radius: 6) .padding(3), alignment: .topTrailing
} else { ).shadow(radius: 6)
Spacer().frame(height:10) } else {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) Spacer().frame(height:10)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!)
.placeholder { .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .placeholder {
.resizable() Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
.scaledToFit() .resizable()
.cornerRadius(10) .scaledToFit()
} .cornerRadius(10)
.frame(width: 100, height: 150) }
.cornerRadius(10) .frame(width: 100, height: 150)
.shadow(radius: 6) .cornerRadius(10)
} .shadow(radius: 6)
Text(item.Name) }
.font(.caption) Text(item.Name)
.fontWeight(.semibold) .font(.caption)
.foregroundColor(.primary) .fontWeight(.semibold)
.lineLimit(1) .foregroundColor(.primary)
Spacer().frame(height:5) .lineLimit(1)
}.frame(width: 100) Spacer().frame(height:5)
}.frame(width: 100)
}
Spacer().frame(width: 14) Spacer().frame(width: 14)
} }
Spacer().frame(width:18) Spacer().frame(width:18)

View File

@ -32,7 +32,7 @@ struct LibrarySearchView: View {
func onAppear() { func onAppear() {
_isLoading.wrappedValue = true; _isLoading.wrappedValue = true;
_items.wrappedValue = []; _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.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json" request.contentType = "application/json"
request.acceptType = "application/json" request.acceptType = "application/json"

View File

@ -274,9 +274,7 @@ struct LibraryView: View {
}.onAppear(perform: listOnAppear).overrideViewPreference(.unspecified).navigationTitle("All Media") }.onAppear(perform: listOnAppear).overrideViewPreference(.unspecified).navigationTitle("All Media")
.toolbar { .toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) { ToolbarItemGroup(placement: .navigationBarTrailing) {
Button { 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) {
print("Search tapped!")
} label: {
Image(systemName: "magnifyingglass") Image(systemName: "magnifyingglass")
} }
} }

View File

@ -75,28 +75,35 @@ struct NextUpView: View {
if(isLoading == false) { if(isLoading == false) {
Spacer().frame(width:18) Spacer().frame(width:18)
ForEach(resumeItems, id: \.Id) { item in ForEach(resumeItems, id: \.Id) { item in
VStack(alignment: .leading) { NavigationLink(destination: ItemView(item: item)) {
Spacer().frame(height:10) VStack(alignment: .leading) {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!) Spacer().frame(height:10)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")!)
.placeholder { .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .placeholder {
.resizable() Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!)
.scaledToFit() .resizable()
.cornerRadius(10) .scaledToFit()
} .cornerRadius(10)
.frame(width: 100, height: 150) }
.cornerRadius(10) .frame(width: 100, height: 150)
.shadow(radius: 6) .cornerRadius(10)
Text(item.SeriesName ?? "") .shadow(radius: 6)
.font(.caption) Text(item.SeriesName ?? "")
.fontWeight(.semibold) .font(.caption)
.foregroundColor(.primary) .fontWeight(.semibold)
.lineLimit(1) .foregroundColor(.primary)
Spacer().frame(height:5) .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) Spacer().frame(width:18)
} }

View File

@ -100,15 +100,13 @@ struct PlayerDemo: View {
_inactivity.wrappedValue = true _inactivity.wrappedValue = true
} }
if((lastPosition == Double(vlcplayer.position) && vlcplayer.state != VLCMediaPlayerState.paused)) { if((lastPosition == Double(vlcplayer.position) && vlcplayer.state != VLCMediaPlayerState.paused)) {
if(iterations > 3) { if(iterations > 5) {
_iterations.wrappedValue = 0; _iterations.wrappedValue = 0;
_streamLoading.wrappedValue = true; _streamLoading.wrappedValue = true;
print("Buffering")
} }
_iterations.wrappedValue+=1; _iterations.wrappedValue+=1;
} else { } else {
_iterations.wrappedValue = 0; _iterations.wrappedValue = 0;
print("Not Buffering")
_streamLoading.wrappedValue = false; _streamLoading.wrappedValue = false;
} }
if(vlcplayer.state == VLCMediaPlayerState.error) { if(vlcplayer.state == VLCMediaPlayerState.error) {
@ -204,7 +202,7 @@ struct PlayerDemo: View {
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json" request.contentType = "application/json"
request.acceptType = "application/json" request.acceptType = "application/json"
request.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<RestResponse<Data>, RestError>) in request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
switch result { switch result {