commit
0786988d96
|
@ -12,74 +12,72 @@ import JellyfinAPI
|
||||||
struct ProgressBar: Shape {
|
struct ProgressBar: Shape {
|
||||||
func path(in rect: CGRect) -> Path {
|
func path(in rect: CGRect) -> Path {
|
||||||
var path = Path()
|
var path = Path()
|
||||||
|
|
||||||
let tl = CGPoint(x: rect.minX, y: rect.minY)
|
let tl = CGPoint(x: rect.minX, y: rect.minY)
|
||||||
let tr = CGPoint(x: rect.maxX, y: rect.minY)
|
let tr = CGPoint(x: rect.maxX, y: rect.minY)
|
||||||
let br = CGPoint(x: rect.maxX, y: rect.maxY)
|
let br = CGPoint(x: rect.maxX, y: rect.maxY)
|
||||||
let bls = CGPoint(x: rect.minX + 10, y: rect.maxY)
|
let bls = CGPoint(x: rect.minX + 10, y: rect.maxY)
|
||||||
let blc = CGPoint(x: rect.minX + 10, y: rect.maxY - 10)
|
let blc = CGPoint(x: rect.minX + 10, y: rect.maxY - 10)
|
||||||
|
|
||||||
path.move(to: tl)
|
path.move(to: tl)
|
||||||
path.addLine(to: tr)
|
path.addLine(to: tr)
|
||||||
path.addLine(to: br)
|
path.addLine(to: br)
|
||||||
path.addLine(to: bls)
|
path.addLine(to: bls)
|
||||||
path.addRelativeArc(center: blc, radius: 10,
|
path.addRelativeArc(center: blc, radius: 10,
|
||||||
startAngle: Angle.degrees(90), delta: Angle.degrees(90))
|
startAngle: Angle.degrees(90), delta: Angle.degrees(90))
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContinueWatchingView: View {
|
struct ContinueWatchingView: View {
|
||||||
var items: [BaseItemDto]
|
var items: [BaseItemDto]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
if items.count > 0 {
|
LazyHStack {
|
||||||
LazyHStack {
|
Spacer().frame(width: 14)
|
||||||
Spacer().frame(width: 14)
|
ForEach(items, id: \.id) { item in
|
||||||
ForEach(items, id: \.id) { item in
|
NavigationLink(destination: ItemView(item: item)) {
|
||||||
NavigationLink(destination: ItemView(item: item)) {
|
VStack(alignment: .leading) {
|
||||||
VStack(alignment: .leading) {
|
Spacer().frame(height: 10)
|
||||||
Spacer().frame(height: 10)
|
ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
||||||
ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
.frame(width: 320, height: 180)
|
||||||
.frame(width: 320, height: 180)
|
.cornerRadius(10)
|
||||||
.cornerRadius(10)
|
.overlay(
|
||||||
.overlay(
|
Group {
|
||||||
Group {
|
if item.type == "Episode" {
|
||||||
if item.type == "Episode" {
|
Text("\(item.name ?? "")")
|
||||||
Text("\(item.name ?? "")")
|
.font(.caption)
|
||||||
.font(.caption)
|
.padding(6)
|
||||||
.padding(6)
|
.foregroundColor(.white)
|
||||||
.foregroundColor(.white)
|
}
|
||||||
}
|
}.background(Color.black)
|
||||||
}.background(Color.black)
|
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
.cornerRadius(10.0)
|
.cornerRadius(10.0)
|
||||||
.padding(6), alignment: .topTrailing
|
.padding(6), alignment: .topTrailing
|
||||||
)
|
)
|
||||||
.overlay(
|
.overlay(
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||||
.mask(ProgressBar())
|
.mask(ProgressBar())
|
||||||
.frame(width: CGFloat((item.userData?.playedPercentage ?? 0) * 3.2), height: 7)
|
.frame(width: CGFloat((item.userData?.playedPercentage ?? 0) * 3.2), height: 7)
|
||||||
.padding(0), alignment: .bottomLeading
|
.padding(0), alignment: .bottomLeading
|
||||||
)
|
)
|
||||||
Text(item.seriesName ?? item.name ?? "")
|
Text(item.seriesName ?? item.name ?? "")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.frame(width: 320, alignment: .leading)
|
.frame(width: 320, alignment: .leading)
|
||||||
Spacer().frame(height: 5)
|
Spacer().frame(height: 5)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 16)
|
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 2)
|
Spacer().frame(width: 16)
|
||||||
}.frame(height: 215)
|
}
|
||||||
|
Spacer().frame(width: 2)
|
||||||
|
}.frame(height: 215)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,17 @@ struct HomeView: View {
|
||||||
@Environment(\.horizontalSizeClass) var hSizeClass
|
@Environment(\.horizontalSizeClass) var hSizeClass
|
||||||
@Environment(\.verticalSizeClass) var vSizeClass
|
@Environment(\.verticalSizeClass) var vSizeClass
|
||||||
@State var showingSettings = false
|
@State var showingSettings = false
|
||||||
|
|
||||||
var body: some View {
|
@ViewBuilder
|
||||||
|
var innerBody: some View {
|
||||||
if(viewModel.isLoading) {
|
if(viewModel.isLoading) {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else {
|
} else {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
Spacer().frame(height: hSizeClass == .compact && vSizeClass == .regular ? 0 : 16)
|
|
||||||
if !viewModel.resumeItems.isEmpty {
|
if !viewModel.resumeItems.isEmpty {
|
||||||
ContinueWatchingView(items: viewModel.resumeItems)
|
ContinueWatchingView(items: viewModel.resumeItems)
|
||||||
|
.padding(.top, hSizeClass == .compact && vSizeClass == .regular ? 0 : 16)
|
||||||
}
|
}
|
||||||
if !viewModel.nextUpItems.isEmpty {
|
if !viewModel.nextUpItems.isEmpty {
|
||||||
NextUpView(items: viewModel.nextUpItems)
|
NextUpView(items: viewModel.nextUpItems)
|
||||||
|
@ -52,10 +53,15 @@ struct HomeView: View {
|
||||||
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
|
||||||
}
|
}
|
||||||
|
.padding(.top, hSizeClass == .compact && vSizeClass == .regular ? 0 : 16)
|
||||||
|
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
innerBody
|
||||||
.navigationTitle(MainTabView.Tab.home.localized)
|
.navigationTitle(MainTabView.Tab.home.localized)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
@ -69,6 +75,5 @@ struct HomeView: View {
|
||||||
.fullScreenCover(isPresented: $showingSettings) {
|
.fullScreenCover(isPresented: $showingSettings) {
|
||||||
SettingsView(viewModel: SettingsViewModel(), close: $showingSettings)
|
SettingsView(viewModel: SettingsViewModel(), close: $showingSettings)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,38 +8,36 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LatestMediaView: View {
|
struct LatestMediaView: View {
|
||||||
@StateObject var viewModel: LatestMediaViewModel
|
@ObservedObject var viewModel: LatestMediaViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
ZStack {
|
LazyHStack {
|
||||||
LazyHStack {
|
Spacer().frame(width: 16)
|
||||||
Spacer().frame(width: 16)
|
ForEach(viewModel.items, id: \.id) { item in
|
||||||
ForEach(viewModel.items, id: \.id) { item in
|
if item.type == "Series" || item.type == "Movie" {
|
||||||
if item.type == "Series" || item.type == "Movie" {
|
NavigationLink(destination: ItemView(item: item)) {
|
||||||
NavigationLink(destination: ItemView(item: item)) {
|
VStack(alignment: .leading) {
|
||||||
VStack(alignment: .leading) {
|
Spacer().frame(height: 10)
|
||||||
Spacer().frame(height: 10)
|
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||||
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
.frame(width: 100, height: 150)
|
||||||
.frame(width: 100, height: 150)
|
.cornerRadius(10)
|
||||||
.cornerRadius(10)
|
Spacer().frame(height: 5)
|
||||||
Spacer().frame(height: 5)
|
Text(item.seriesName ?? item.name ?? "")
|
||||||
Text(item.seriesName ?? item.name ?? "")
|
.font(.caption)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.lineLimit(1)
|
||||||
|
if item.productionYear != nil {
|
||||||
|
Text(String(item.productionYear ?? 0))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.primary)
|
} else {
|
||||||
.lineLimit(1)
|
Text(item.type!)
|
||||||
if item.productionYear != nil {
|
}
|
||||||
Text(String(item.productionYear ?? 0))
|
}.frame(width: 100)
|
||||||
.foregroundColor(.secondary)
|
Spacer().frame(width: 15)
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
} else {
|
|
||||||
Text(item.type!)
|
|
||||||
}
|
|
||||||
}.frame(width: 100)
|
|
||||||
Spacer().frame(width: 15)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,44 +10,42 @@ import Combine
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
struct NextUpView: View {
|
struct NextUpView: View {
|
||||||
|
|
||||||
var items: [BaseItemDto]
|
var items: [BaseItemDto]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if items.count != 0 {
|
Text("Next Up")
|
||||||
Text("Next Up")
|
.font(.title2)
|
||||||
.font(.title2)
|
.fontWeight(.bold)
|
||||||
.fontWeight(.bold)
|
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||||
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
LazyHStack {
|
||||||
LazyHStack {
|
Spacer().frame(width: 16)
|
||||||
Spacer().frame(width: 16)
|
ForEach(items, id: \.id) { item in
|
||||||
ForEach(items, id: \.id) { item in
|
NavigationLink(destination: ItemView(item: item)) {
|
||||||
NavigationLink(destination: ItemView(item: item)) {
|
VStack(alignment: .leading) {
|
||||||
VStack(alignment: .leading) {
|
ImageView(src: item.getSeriesPrimaryImage(maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash())
|
||||||
ImageView(src: item.getSeriesPrimaryImage(maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash())
|
.frame(width: 100, height: 150)
|
||||||
.frame(width: 100, height: 150)
|
.cornerRadius(10)
|
||||||
.cornerRadius(10)
|
Spacer().frame(height: 5)
|
||||||
Spacer().frame(height: 5)
|
Text(item.seriesName!)
|
||||||
Text(item.seriesName!)
|
.font(.caption)
|
||||||
.font(.caption)
|
.fontWeight(.semibold)
|
||||||
.fontWeight(.semibold)
|
.foregroundColor(.primary)
|
||||||
.foregroundColor(.primary)
|
.lineLimit(1)
|
||||||
.lineLimit(1)
|
Text("S\(item.parentIndexNumber ?? 0):E\(item.indexNumber ?? 0)")
|
||||||
Text("S\(item.parentIndexNumber ?? 0):E\(item.indexNumber ?? 0)")
|
.font(.caption)
|
||||||
.font(.caption)
|
.fontWeight(.semibold)
|
||||||
.fontWeight(.semibold)
|
.foregroundColor(.secondary)
|
||||||
.foregroundColor(.secondary)
|
.lineLimit(1)
|
||||||
.lineLimit(1)
|
}.frame(width: 100)
|
||||||
}.frame(width: 100)
|
Spacer().frame(width: 16)
|
||||||
Spacer().frame(width: 16)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 200)
|
|
||||||
}
|
}
|
||||||
|
.frame(height: 200)
|
||||||
}
|
}
|
||||||
.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
func getPublicUsers() {
|
func getPublicUsers() {
|
||||||
if ServerEnvironment.current.server != nil {
|
if ServerEnvironment.current.server != nil {
|
||||||
UserAPI.getPublicUsers()
|
UserAPI.getPublicUsers()
|
||||||
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
self.handleAPIRequestCompletion(completion: completion)
|
self.handleAPIRequestCompletion(completion: completion)
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
|
@ -56,6 +57,7 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
|
|
||||||
func connectToServer() {
|
func connectToServer() {
|
||||||
ServerEnvironment.current.create(with: uriSubject.value)
|
ServerEnvironment.current.create(with: uriSubject.value)
|
||||||
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { result in
|
.sink(receiveCompletion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
|
@ -71,6 +73,7 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
|
|
||||||
func login() {
|
func login() {
|
||||||
SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value)
|
SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value)
|
||||||
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
switch completion {
|
switch completion {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
|
Loading…
Reference in New Issue