yeah ops
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run

This commit is contained in:
cranci1 2025-03-04 17:42:58 +01:00
parent e13333afe7
commit eb65912998

View file

@ -40,233 +40,236 @@ struct HomeView: View {
} }
var body: some View { var body: some View {
NavigationStack { NavigationView {
ScrollView { VStack {
if !continueWatchingItems.isEmpty { ScrollView {
LazyVStack(alignment: .leading) { if !continueWatchingItems.isEmpty {
Text("Continue Watching") LazyVStack(alignment: .leading) {
.font(.headline) Text("Continue Watching")
.padding(.horizontal, 8) .font(.headline)
ScrollView(.horizontal, showsIndicators: false) { .padding(.horizontal, 8)
HStack(spacing: 8) { ScrollView(.horizontal, showsIndicators: false) {
ForEach(Array(continueWatchingItems.reversed())) { item in HStack(spacing: 8) {
Button(action: { ForEach(Array(continueWatchingItems.reversed())) { item in
if UserDefaults.standard.string(forKey: "externalPlayer") == "Sora" { Button(action: {
let customMediaPlayer = CustomMediaPlayerViewController( if UserDefaults.standard.string(forKey: "externalPlayer") == "Sora" {
module: item.module, let customMediaPlayer = CustomMediaPlayerViewController(
urlString: item.streamUrl, module: item.module,
fullUrl: item.fullUrl, urlString: item.streamUrl,
title: item.mediaTitle, fullUrl: item.fullUrl,
episodeNumber: item.episodeNumber, title: item.mediaTitle,
onWatchNext: { }, episodeNumber: item.episodeNumber,
subtitlesURL: item.subtitles, onWatchNext: { },
episodeImageUrl: item.imageUrl subtitlesURL: item.subtitles,
) episodeImageUrl: item.imageUrl
customMediaPlayer.modalPresentationStyle = .fullScreen )
customMediaPlayer.modalPresentationStyle = .fullScreen
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
findTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil) let rootVC = windowScene.windows.first?.rootViewController {
findTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil)
}
} else {
let videoPlayerViewController = VideoPlayerViewController(module: item.module)
videoPlayerViewController.streamUrl = item.streamUrl
videoPlayerViewController.fullUrl = item.fullUrl
videoPlayerViewController.episodeImageUrl = item.imageUrl
videoPlayerViewController.episodeNumber = item.episodeNumber
videoPlayerViewController.mediaTitle = item.mediaTitle
videoPlayerViewController.subtitles = item.subtitles ?? ""
videoPlayerViewController.modalPresentationStyle = .fullScreen
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
}
} }
} else { }) {
let videoPlayerViewController = VideoPlayerViewController(module: item.module) VStack(alignment: .leading) {
videoPlayerViewController.streamUrl = item.streamUrl ZStack {
videoPlayerViewController.fullUrl = item.fullUrl KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl))
videoPlayerViewController.episodeImageUrl = item.imageUrl .placeholder {
videoPlayerViewController.episodeNumber = item.episodeNumber RoundedRectangle(cornerRadius: 10)
videoPlayerViewController.mediaTitle = item.mediaTitle .fill(Color.gray.opacity(0.3))
videoPlayerViewController.subtitles = item.subtitles ?? "" .frame(width: 240, height: 135)
videoPlayerViewController.modalPresentationStyle = .fullScreen .shimmering()
}
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, .setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
let rootVC = windowScene.windows.first?.rootViewController { .resizable()
findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil) .aspectRatio(16/9, contentMode: .fill)
.frame(width: 240, height: 135)
.cornerRadius(10)
.clipped()
.overlay(
KFImage(URL(string: item.module.metadata.iconUrl))
.resizable()
.frame(width: 24, height: 24)
.cornerRadius(4)
.padding(4),
alignment: .topLeading
)
}
.overlay(
ZStack {
Rectangle()
.fill(Color.black.opacity(0.3))
.blur(radius: 3)
.frame(height: 30)
ProgressView(value: item.progress)
.progressViewStyle(LinearProgressViewStyle(tint: .white))
.padding(.horizontal, 8)
.scaleEffect(x: 1, y: 1.5, anchor: .center)
},
alignment: .bottom
)
VStack(alignment: .leading) {
Text("Episode \(item.episodeNumber)")
.font(.caption)
.lineLimit(1)
.foregroundColor(.secondary)
Text(item.mediaTitle)
.font(.caption)
.lineLimit(2)
.foregroundColor(.primary)
.multilineTextAlignment(.leading)
}
.padding(.horizontal, 8)
}
.frame(width: 250, height: 190)
}
.contextMenu {
Button(action: { markContinueWatchingItemAsWatched(item: item) }) {
Label("Mark as Watched", systemImage: "checkmark.circle")
}
Button(role: .destructive, action: { removeContinueWatchingItem(item: item) }) {
Label("Remove Item", systemImage: "trash")
} }
} }
}) { }
VStack(alignment: .leading) { }
ZStack { .padding(.horizontal, 8)
KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl)) }
.frame(height: 190)
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(alignment: .bottom, spacing: 5) {
Text("Seasonal")
.font(.headline)
Text("of \(currentDeviceSeasonAndYear.season) \(String(format: "%d", currentDeviceSeasonAndYear.year))")
.font(.subheadline)
.foregroundColor(.gray)
}
.padding(.horizontal, 8)
.padding(.top, 8)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
if aniListItems.isEmpty {
ForEach(0..<5, id: \.self) { _ in
HomeSkeletonCell()
}
} else {
ForEach(aniListItems, id: \.id) { item in
NavigationLink(destination: AniListDetailsView(animeID: item.id)) {
VStack {
KFImage(URL(string: item.coverImage.large))
.placeholder { .placeholder {
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3)) .fill(Color.gray.opacity(0.3))
.frame(width: 240, height: 135) .frame(width: 130, height: 195)
.shimmering() .shimmering()
} }
.setProcessor(RoundCornerImageProcessor(cornerRadius: 10)) .setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
.resizable() .resizable()
.aspectRatio(16/9, contentMode: .fill) .scaledToFill()
.frame(width: 240, height: 135) .frame(width: 130, height: 195)
.cornerRadius(10) .cornerRadius(10)
.clipped() .clipped()
.overlay(
KFImage(URL(string: item.module.metadata.iconUrl))
.resizable()
.frame(width: 24, height: 24)
.cornerRadius(4)
.padding(4),
alignment: .topLeading
)
}
.overlay(
ZStack {
Rectangle()
.fill(Color.black.opacity(0.3))
.blur(radius: 3)
.frame(height: 30)
ProgressView(value: item.progress)
.progressViewStyle(LinearProgressViewStyle(tint: .white))
.padding(.horizontal, 8)
.scaleEffect(x: 1, y: 1.5, anchor: .center)
},
alignment: .bottom
)
VStack(alignment: .leading) {
Text("Episode \(item.episodeNumber)")
.font(.caption)
.lineLimit(1)
.foregroundColor(.secondary)
Text(item.mediaTitle) Text(item.title.romaji)
.font(.caption) .font(.caption)
.lineLimit(2) .frame(width: 130)
.lineLimit(1)
.multilineTextAlignment(.center)
.foregroundColor(.primary) .foregroundColor(.primary)
.multilineTextAlignment(.leading)
} }
.padding(.horizontal, 8)
}
.frame(width: 250, height: 190)
}
.contextMenu {
Button(action: { markContinueWatchingItemAsWatched(item: item) }) {
Label("Mark as Watched", systemImage: "checkmark.circle")
}
Button(role: .destructive, action: { removeContinueWatchingItem(item: item) }) {
Label("Remove Item", systemImage: "trash")
} }
} }
} }
} }
.padding(.horizontal, 8) .padding(.horizontal, 8)
} }
.frame(height: 190)
} HStack(alignment: .bottom, spacing: 5) {
} Text("Trending")
VStack(alignment: .leading, spacing: 16) { .font(.headline)
HStack(alignment: .bottom, spacing: 5) { Text("on \(trendingDateString)")
Text("Seasonal") .font(.subheadline)
.font(.headline) .foregroundColor(.gray)
Text("of \(currentDeviceSeasonAndYear.season) \(String(format: "%d", currentDeviceSeasonAndYear.year))") }
.font(.subheadline) .padding(.horizontal, 8)
.foregroundColor(.gray)
} ScrollView(.horizontal, showsIndicators: false) {
.padding(.horizontal, 8) HStack(spacing: 8) {
.padding(.top, 8) if trendingItems.isEmpty {
ForEach(0..<5, id: \.self) { _ in
ScrollView(.horizontal, showsIndicators: false) { HomeSkeletonCell()
HStack(spacing: 8) { }
if aniListItems.isEmpty { } else {
ForEach(0..<5, id: \.self) { _ in ForEach(trendingItems, id: \.id) { item in
HomeSkeletonCell() NavigationLink(destination: AniListDetailsView(animeID: item.id)) {
} VStack {
} else { KFImage(URL(string: item.coverImage.large))
ForEach(aniListItems, id: \.id) { item in .placeholder {
NavigationLink(destination: AniListDetailsView(animeID: item.id)) { RoundedRectangle(cornerRadius: 10)
VStack { .fill(Color.gray.opacity(0.3))
KFImage(URL(string: item.coverImage.large)) .frame(width: 130, height: 195)
.placeholder { .shimmering()
RoundedRectangle(cornerRadius: 10) }
.fill(Color.gray.opacity(0.3)) .setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
.frame(width: 130, height: 195) .resizable()
.shimmering() .scaledToFill()
} .frame(width: 130, height: 195)
.setProcessor(RoundCornerImageProcessor(cornerRadius: 10)) .cornerRadius(10)
.resizable() .clipped()
.scaledToFill()
.frame(width: 130, height: 195) Text(item.title.romaji)
.cornerRadius(10) .font(.caption)
.clipped() .frame(width: 130)
.lineLimit(1)
Text(item.title.romaji) .multilineTextAlignment(.center)
.font(.caption) .foregroundColor(.primary)
.frame(width: 130) }
.lineLimit(1)
.multilineTextAlignment(.center)
.foregroundColor(.primary)
} }
} }
} }
} }
.padding(.horizontal, 8)
} }
.padding(.horizontal, 8)
} }
.padding(.bottom, 16)
HStack(alignment: .bottom, spacing: 5) { }
Text("Trending") .navigationTitle("Home")
.font(.headline) }
Text("on \(trendingDateString)") .onAppear {
.font(.subheadline) continueWatchingItems = ContinueWatchingManager.shared.fetchItems()
.foregroundColor(.gray) AnilistServiceSeasonalAnime().fetchSeasonalAnime { items in
} if let items = items {
.padding(.horizontal, 8) aniListItems = items
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
if trendingItems.isEmpty {
ForEach(0..<5, id: \.self) { _ in
HomeSkeletonCell()
}
} else {
ForEach(trendingItems, id: \.id) { item in
NavigationLink(destination: AniListDetailsView(animeID: item.id)) {
VStack {
KFImage(URL(string: item.coverImage.large))
.placeholder {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3))
.frame(width: 130, height: 195)
.shimmering()
}
.setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
.resizable()
.scaledToFill()
.frame(width: 130, height: 195)
.cornerRadius(10)
.clipped()
Text(item.title.romaji)
.font(.caption)
.frame(width: 130)
.lineLimit(1)
.multilineTextAlignment(.center)
.foregroundColor(.primary)
}
}
}
}
}
.padding(.horizontal, 8)
} }
} }
.padding(.bottom, 16) AnilistServiceTrendingAnime().fetchTrendingAnime { items in
} if let items = items {
.navigationTitle("Home") trendingItems = items
} }
.onAppear {
continueWatchingItems = ContinueWatchingManager.shared.fetchItems()
AnilistServiceSeasonalAnime().fetchSeasonalAnime { items in
if let items = items {
aniListItems = items
}
}
AnilistServiceTrendingAnime().fetchTrendingAnime { items in
if let items = items {
trendingItems = items
} }
} }
} }
.navigationViewStyle(StackNavigationViewStyle())
} }
private func markContinueWatchingItemAsWatched(item: ContinueWatchingItem) { private func markContinueWatchingItemAsWatched(item: ContinueWatchingItem) {