mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 00:22:12 +00:00
Rewrote & removed extra code
This commit is contained in:
parent
ada1332370
commit
d195df3dff
1 changed files with 62 additions and 159 deletions
|
|
@ -8,113 +8,8 @@
|
||||||
import AVKit
|
import AVKit
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
|
||||||
|
|
||||||
struct DownloadView: View {
|
struct DownloadView: View {
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Helper to present the custom player for a downloaded asset
|
|
||||||
static func presentPlayer(for asset: DownloadedAsset, jsController: JSController) {
|
|
||||||
// Verify local files exist before attempting playback
|
|
||||||
guard jsController.verifyAssetFileExists(asset) else { return }
|
|
||||||
|
|
||||||
// Determine stream type based on local file extension
|
|
||||||
let streamType = asset.localURL.pathExtension.lowercased() == "mp4" ? "mp4" : "hls"
|
|
||||||
|
|
||||||
// Build a minimal module/metadata for the local playback context
|
|
||||||
let dummyMetadata = ModuleMetadata(
|
|
||||||
sourceName: "",
|
|
||||||
author: ModuleMetadata.Author(name: "", icon: ""),
|
|
||||||
iconUrl: "",
|
|
||||||
version: "",
|
|
||||||
language: "",
|
|
||||||
baseUrl: "",
|
|
||||||
streamType: streamType,
|
|
||||||
quality: "",
|
|
||||||
searchBaseUrl: "",
|
|
||||||
scriptUrl: "",
|
|
||||||
asyncJS: nil,
|
|
||||||
streamAsyncJS: nil,
|
|
||||||
softsub: nil,
|
|
||||||
multiStream: nil,
|
|
||||||
multiSubs: nil,
|
|
||||||
type: nil,
|
|
||||||
novel: false
|
|
||||||
)
|
|
||||||
|
|
||||||
let dummyModule = ScrapingModule(
|
|
||||||
metadata: dummyMetadata,
|
|
||||||
localPath: "",
|
|
||||||
metadataUrl: ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pre-compute the show/season group to determine total available downloaded episodes
|
|
||||||
let showTitle = asset.metadata?.showTitle ?? asset.name
|
|
||||||
let seasonNumber = asset.metadata?.seasonNumber
|
|
||||||
let group = jsController.savedAssets
|
|
||||||
.filter { a in
|
|
||||||
let aTitle = a.metadata?.showTitle ?? a.name
|
|
||||||
let sameTitle = (aTitle == showTitle)
|
|
||||||
let sameSeason = (seasonNumber == nil) || (a.metadata?.seasonNumber == seasonNumber)
|
|
||||||
return sameTitle && sameSeason
|
|
||||||
}
|
|
||||||
.sorted { (a, b) in
|
|
||||||
let ae = a.metadata?.episode ?? 0
|
|
||||||
let be = b.metadata?.episode ?? 0
|
|
||||||
return ae < be
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalEpisodes = group.count
|
|
||||||
|
|
||||||
let customPlayer = CustomMediaPlayerViewController(
|
|
||||||
module: dummyModule,
|
|
||||||
urlString: asset.localURL.absoluteString,
|
|
||||||
fullUrl: asset.originalURL.absoluteString,
|
|
||||||
title: asset.metadata?.showTitle ?? asset.name,
|
|
||||||
episodeNumber: asset.metadata?.episode ?? 0,
|
|
||||||
episodeTitle: asset.metadata?.episodeTitle ?? "",
|
|
||||||
seasonNumber: asset.metadata?.seasonNumber ?? 1,
|
|
||||||
onWatchNext: {
|
|
||||||
// Find the next downloaded episode within the same show (and season if available)
|
|
||||||
let currentEp = asset.metadata?.episode ?? 0
|
|
||||||
let all = jsController.savedAssets
|
|
||||||
let candidates = all
|
|
||||||
.filter { a in
|
|
||||||
let aTitle = a.metadata?.showTitle ?? a.name
|
|
||||||
let sameTitle = (aTitle == showTitle)
|
|
||||||
let sameSeason = (seasonNumber == nil) || (a.metadata?.seasonNumber == seasonNumber)
|
|
||||||
return sameTitle && sameSeason && (a.metadata?.episode ?? 0) > currentEp
|
|
||||||
}
|
|
||||||
.sorted { (a, b) in
|
|
||||||
let ae = a.metadata?.episode ?? 0
|
|
||||||
let be = b.metadata?.episode ?? 0
|
|
||||||
return ae < be
|
|
||||||
}
|
|
||||||
|
|
||||||
if let next = candidates.first {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
DownloadView.presentPlayer(for: next, jsController: jsController)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No later downloaded episode found – optionally show a lightweight notification
|
|
||||||
// (silent no-op to avoid additional imports)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
subtitlesURL: asset.localSubtitleURL?.absoluteString,
|
|
||||||
aniListID: 0,
|
|
||||||
totalEpisodes: totalEpisodes,
|
|
||||||
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
|
||||||
headers: nil
|
|
||||||
)
|
|
||||||
|
|
||||||
customPlayer.modalPresentationStyle = UIModalPresentationStyle.fullScreen
|
|
||||||
|
|
||||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
||||||
let rootViewController = windowScene.windows.first?.rootViewController {
|
|
||||||
rootViewController.present(customPlayer, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnvironmentObject var jsController: JSController
|
@EnvironmentObject var jsController: JSController
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@State private var selectedTab = 0
|
@State private var selectedTab = 0
|
||||||
|
|
@ -194,7 +89,7 @@ struct DownloadView: View {
|
||||||
downloads: jsController.downloadQueue
|
downloads: jsController.downloadQueue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !jsController.activeDownloads.isEmpty {
|
if !jsController.activeDownloads.isEmpty {
|
||||||
DownloadSectionView(
|
DownloadSectionView(
|
||||||
title: NSLocalizedString("Active Downloads", comment: ""),
|
title: NSLocalizedString("Active Downloads", comment: ""),
|
||||||
|
|
@ -203,40 +98,76 @@ struct DownloadView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.top, 20)
|
||||||
.padding(.vertical, 16)
|
.scrollViewBottomPadding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var emptyActiveDownloadsView: some View {
|
|
||||||
VStack(spacing: 16) {
|
private var downloadedContentView: some View {
|
||||||
Image(systemName: "arrow.down.circle")
|
Group {
|
||||||
.font(.largeTitle)
|
if filteredAndSortedAssets.isEmpty {
|
||||||
.foregroundStyle(.secondary)
|
emptyDownloadsView
|
||||||
Text(NSLocalizedString("No Active Downloads", comment: ""))
|
} else {
|
||||||
.font(.title3)
|
ScrollView(showsIndicators: false) {
|
||||||
.fontWeight(.medium)
|
VStack(spacing: 20) {
|
||||||
.foregroundStyle(.primary)
|
DownloadSummaryCard(
|
||||||
Text(NSLocalizedString("When you start a download it will appear here.", comment: ""))
|
totalShows: groupedAssets.count,
|
||||||
.font(.subheadline)
|
totalEpisodes: filteredAndSortedAssets.count,
|
||||||
.foregroundStyle(.secondary)
|
totalSize: filteredAndSortedAssets.reduce(0) { $0 + $1.fileSize }
|
||||||
.multilineTextAlignment(.center)
|
)
|
||||||
|
|
||||||
|
DownloadedSection(
|
||||||
|
groups: groupedAssets,
|
||||||
|
onDelete: { asset in
|
||||||
|
assetToDelete = asset
|
||||||
|
showDeleteAlert = true
|
||||||
|
},
|
||||||
|
onPlay: playAsset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
.scrollViewBottomPadding()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
|
||||||
.padding(.horizontal, 40)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var emptyDownloadsView: some View {
|
private var emptyActiveDownloadsView: some View {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
Image(systemName: "arrow.down.circle")
|
Image(systemName: "arrow.down.circle")
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Text(NSLocalizedString("No Active Downloads", comment: ""))
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
|
Text(NSLocalizedString("Actively downloading media can be tracked from here.", comment: ""))
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||||
|
.padding(.horizontal, 40)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var emptyDownloadsView: some View {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
Image(systemName: "arrow.down.circle")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
Text(NSLocalizedString("No Downloads", comment: ""))
|
Text(NSLocalizedString("No Downloads", comment: ""))
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
Text(NSLocalizedString("Your downloaded episodes will appear here", comment: ""))
|
Text(NSLocalizedString("Your downloaded episodes will appear here", comment: ""))
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
@ -246,26 +177,7 @@ struct DownloadView: View {
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||||
.padding(.horizontal, 40)
|
.padding(.horizontal, 40)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var downloadedContentView: some View {
|
|
||||||
Group {
|
|
||||||
if jsController.savedAssets.isEmpty {
|
|
||||||
emptyDownloadsView
|
|
||||||
} else {
|
|
||||||
ScrollView(showsIndicators: false) {
|
|
||||||
VStack(spacing: 16) {
|
|
||||||
DownloadedSection(
|
|
||||||
groups: groupedAssets,
|
|
||||||
onDelete: { asset in jsController.deleteAsset(asset) },
|
|
||||||
onPlay: { asset in playAsset(asset) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
.padding(.vertical, 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var filteredAndSortedAssets: [DownloadedAsset] {
|
private var filteredAndSortedAssets: [DownloadedAsset] {
|
||||||
let filtered = searchText.isEmpty
|
let filtered = searchText.isEmpty
|
||||||
? jsController.savedAssets
|
? jsController.savedAssets
|
||||||
|
|
@ -329,16 +241,6 @@ struct DownloadView: View {
|
||||||
localPath: "",
|
localPath: "",
|
||||||
metadataUrl: ""
|
metadataUrl: ""
|
||||||
)
|
)
|
||||||
// Determine total number of downloaded episodes in this show/season for UI
|
|
||||||
let _showTitle = asset.metadata?.showTitle ?? asset.name
|
|
||||||
let _seasonNumber = asset.metadata?.seasonNumber
|
|
||||||
let _group = jsController.savedAssets.filter { a in
|
|
||||||
let aTitle = a.metadata?.showTitle ?? a.name
|
|
||||||
let sameTitle = (aTitle == _showTitle)
|
|
||||||
let sameSeason = (_seasonNumber == nil) || (a.metadata?.seasonNumber == _seasonNumber)
|
|
||||||
return sameTitle && sameSeason
|
|
||||||
}
|
|
||||||
let _totalEpisodes = _group.count
|
|
||||||
|
|
||||||
let customPlayer = CustomMediaPlayerViewController(
|
let customPlayer = CustomMediaPlayerViewController(
|
||||||
module: dummyModule,
|
module: dummyModule,
|
||||||
|
|
@ -352,7 +254,7 @@ struct DownloadView: View {
|
||||||
let showTitle = asset.metadata?.showTitle ?? asset.name
|
let showTitle = asset.metadata?.showTitle ?? asset.name
|
||||||
let seasonNumber = asset.metadata?.seasonNumber
|
let seasonNumber = asset.metadata?.seasonNumber
|
||||||
let currentEp = asset.metadata?.episode ?? 0
|
let currentEp = asset.metadata?.episode ?? 0
|
||||||
let candidates = jsController.savedAssets
|
let next = jsController.savedAssets
|
||||||
.filter { a in
|
.filter { a in
|
||||||
let aTitle = a.metadata?.showTitle ?? a.name
|
let aTitle = a.metadata?.showTitle ?? a.name
|
||||||
let sameTitle = (aTitle == showTitle)
|
let sameTitle = (aTitle == showTitle)
|
||||||
|
|
@ -364,15 +266,16 @@ struct DownloadView: View {
|
||||||
let be = b.metadata?.episode ?? 0
|
let be = b.metadata?.episode ?? 0
|
||||||
return ae < be
|
return ae < be
|
||||||
}
|
}
|
||||||
if let next = candidates.first {
|
.first
|
||||||
|
if let next = next {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
DownloadView.presentPlayer(for: next, jsController: jsController)
|
self.playAsset(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subtitlesURL: asset.localSubtitleURL?.absoluteString,
|
subtitlesURL: asset.localSubtitleURL?.absoluteString,
|
||||||
aniListID: 0,
|
aniListID: 0,
|
||||||
totalEpisodes: _totalEpisodes,
|
totalEpisodes: asset.metadata?.episode ?? 0,
|
||||||
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
||||||
headers: nil
|
headers: nil
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue