mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
continue watching items added working fine
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
This commit is contained in:
parent
01f90882f9
commit
07ad7b80e5
7 changed files with 182 additions and 3 deletions
|
|
@ -38,6 +38,8 @@
|
|||
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */; };
|
||||
1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD52D3AB3DB00E97C31 /* Logger.swift */; };
|
||||
13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B7F4C02D58FFDD0045714A /* Shimmer.swift */; };
|
||||
13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */; };
|
||||
13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */; };
|
||||
13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CBEFD92D5F7D1200D011EE /* String.swift */; };
|
||||
13D842522D4523B800EBBFA6 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 13D842512D4523B800EBBFA6 /* Drops */; };
|
||||
13D842552D45267500EBBFA6 /* DropManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D842542D45267500EBBFA6 /* DropManager.swift */; };
|
||||
|
|
@ -84,6 +86,8 @@
|
|||
1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = "<group>"; };
|
||||
1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
13B7F4C02D58FFDD0045714A /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
|
||||
13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingManager.swift; sourceTree = "<group>"; };
|
||||
13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingItem.swift; sourceTree = "<group>"; };
|
||||
13CBEFD92D5F7D1200D011EE /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
13D842542D45267500EBBFA6 /* DropManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropManager.swift; sourceTree = "<group>"; };
|
||||
13D99CF62D4E73C300250A86 /* ModuleAdditionSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleAdditionSettingsView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -232,6 +236,7 @@
|
|||
133D7C852D2BE2640075467E /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13C0E5E82D5F85DD00E7F619 /* ContinueWatching */,
|
||||
13103E8C2D58E037000F0673 /* SkeletonCells */,
|
||||
13DC0C442D302C6A00D0F966 /* MediaPlayer */,
|
||||
133D7C862D2BE2640075467E /* Extensions */,
|
||||
|
|
@ -323,6 +328,15 @@
|
|||
path = SettingsView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13C0E5E82D5F85DD00E7F619 /* ContinueWatching */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */,
|
||||
13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */,
|
||||
);
|
||||
path = ContinueWatching;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13D842532D45266900EBBFA6 /* Drops */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -459,6 +473,7 @@
|
|||
136F21B92D5B8DD8006409AC /* AniList-MediaInfo.swift in Sources */,
|
||||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
||||
13D99CF72D4E73C300250A86 /* ModuleAdditionSettingsView.swift in Sources */,
|
||||
13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */,
|
||||
13103E862D58A328000F0673 /* AniList-Trending.swift in Sources */,
|
||||
13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */,
|
||||
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */,
|
||||
|
|
@ -482,6 +497,7 @@
|
|||
138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */,
|
||||
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */,
|
||||
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */,
|
||||
13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
|||
19
Sora/Utils/ContinueWatching/ContinueWatchingItem.swift
Normal file
19
Sora/Utils/ContinueWatching/ContinueWatchingItem.swift
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ContinueWatchingItem.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 14/02/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ContinueWatchingItem: Codable, Identifiable {
|
||||
let id: UUID
|
||||
let imageUrl: String
|
||||
let episodeNumber: Int
|
||||
let mediaTitle: String
|
||||
let progress: Double
|
||||
let streamUrl: String
|
||||
let fullUrl: String
|
||||
let module: ScrapingModule
|
||||
}
|
||||
35
Sora/Utils/ContinueWatching/ContinueWatchingManager.swift
Normal file
35
Sora/Utils/ContinueWatching/ContinueWatchingManager.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// ContinueWatchingManager.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 14/02/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ContinueWatchingManager {
|
||||
static let shared = ContinueWatchingManager()
|
||||
private let storageKey = "continueWatchingItems"
|
||||
|
||||
private init() {}
|
||||
|
||||
func save(item: ContinueWatchingItem) {
|
||||
var items = fetchItems()
|
||||
if let index = items.firstIndex(where: { $0.streamUrl == item.streamUrl && $0.episodeNumber == item.episodeNumber }) {
|
||||
items[index] = item
|
||||
} else {
|
||||
items.append(item)
|
||||
}
|
||||
if let data = try? JSONEncoder().encode(items) {
|
||||
UserDefaults.standard.set(data, forKey: storageKey)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchItems() -> [ContinueWatchingItem] {
|
||||
if let data = UserDefaults.standard.data(forKey: storageKey),
|
||||
let items = try? JSONDecoder().decode([ContinueWatchingItem].self, from: data) {
|
||||
return items
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,10 @@ class VideoPlayerViewController: UIViewController {
|
|||
var streamUrl: String?
|
||||
var fullUrl: String = ""
|
||||
|
||||
var episodeNumber: Int = 0
|
||||
var episodeImageUrl: String = ""
|
||||
var mediaTitle: String = ""
|
||||
|
||||
init(module: ScrapingModule) {
|
||||
self.module = module
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
|
@ -80,6 +84,24 @@ class VideoPlayerViewController: UIViewController {
|
|||
player?.removeTimeObserver(timeObserverToken)
|
||||
self.timeObserverToken = nil
|
||||
}
|
||||
|
||||
if let currentItem = player?.currentItem, currentItem.duration.seconds > 0,
|
||||
let streamUrl = streamUrl {
|
||||
let currentTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)")
|
||||
let duration = currentItem.duration.seconds
|
||||
let progress = currentTime / duration
|
||||
let item = ContinueWatchingItem(
|
||||
id: UUID(),
|
||||
imageUrl: episodeImageUrl,
|
||||
episodeNumber: episodeNumber,
|
||||
mediaTitle: mediaTitle,
|
||||
progress: progress,
|
||||
streamUrl: streamUrl,
|
||||
fullUrl: fullUrl,
|
||||
module: module
|
||||
)
|
||||
ContinueWatchingManager.shared.save(item: item)
|
||||
}
|
||||
}
|
||||
|
||||
private func setInitialPlayerRate() {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Kingfisher
|
|||
struct HomeView: View {
|
||||
@State private var aniListItems: [AniListItem] = []
|
||||
@State private var trendingItems: [AniListItem] = []
|
||||
@State private var continueWatchingItems: [ContinueWatchingItem] = []
|
||||
|
||||
private var currentDeviceSeasonAndYear: (season: String, year: Int) {
|
||||
let currentDate = Date()
|
||||
|
|
@ -136,12 +137,88 @@ struct HomeView: View {
|
|||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
if !continueWatchingItems.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Continue Watching")
|
||||
.font(.headline)
|
||||
.padding(.horizontal, 8)
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(continueWatchingItems) { item in
|
||||
Button(action: {
|
||||
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.modalPresentationStyle = .fullScreen
|
||||
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let rootVC = windowScene.windows.first?.rootViewController {
|
||||
rootVC.present(videoPlayerViewController, animated: true, completion: nil)
|
||||
}
|
||||
}) {
|
||||
VStack {
|
||||
ZStack {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.placeholder {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: 240, height: 135)
|
||||
.shimmering()
|
||||
}
|
||||
.setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
|
||||
.resizable()
|
||||
.aspectRatio(16/9, contentMode: .fill)
|
||||
.frame(width: 240, height: 135)
|
||||
.cornerRadius(10)
|
||||
.clipped()
|
||||
}
|
||||
.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: 2, 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: 200)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.navigationTitle("Home")
|
||||
}
|
||||
.onAppear {
|
||||
continueWatchingItems = ContinueWatchingManager.shared.fetchItems()
|
||||
AnilistServiceSeasonalAnime().fetchSeasonalAnime { items in
|
||||
if let items = items {
|
||||
aniListItems = items
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct EpisodeCell: View {
|
|||
@State private var episodeImageUrl: String = ""
|
||||
@State private var isLoading: Bool = true
|
||||
@State private var currentProgress: Double = 0.0
|
||||
let onTap: (String) -> Void
|
||||
|
||||
private func markAsWatched() {
|
||||
UserDefaults.standard.set(99999999.0, forKey: "lastPlayedTime_\(episode)")
|
||||
|
|
@ -90,6 +91,9 @@ struct EpisodeCell: View {
|
|||
fetchEpisodeDetails()
|
||||
updateProgress()
|
||||
}
|
||||
.onTapGesture {
|
||||
onTap(episodeImageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchEpisodeDetails() {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ struct MediaInfoView: View {
|
|||
@State var isFetchingEpisode: Bool = false
|
||||
|
||||
@State private var selectedEpisodeNumber: Int = 0
|
||||
@State private var selectedEpisodeImage: String = ""
|
||||
|
||||
@AppStorage("externalPlayer") private var externalPlayer: String = "Default"
|
||||
@AppStorage("episodeChunkSize") private var episodeChunkSize: Int = 100
|
||||
|
|
@ -207,14 +208,16 @@ struct MediaInfoView: View {
|
|||
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
|
||||
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
|
||||
|
||||
EpisodeCell(episode: ep.href, episodeID: ep.number - 1, progress: progress, itemID: itemID ?? 0)
|
||||
.onTapGesture {
|
||||
EpisodeCell(episode: ep.href, episodeID: ep.number - 1, progress: progress, itemID: itemID ?? 0, onTap: { imageUrl in
|
||||
if !isFetchingEpisode {
|
||||
isFetchingEpisode = true
|
||||
selectedEpisodeNumber = ep.number
|
||||
selectedEpisodeImage = imageUrl
|
||||
fetchStream(href: ep.href)
|
||||
}
|
||||
}
|
||||
.disabled(isFetchingEpisode)
|
||||
)
|
||||
.disabled(isFetchingEpisode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -479,6 +482,9 @@ struct MediaInfoView: View {
|
|||
let videoPlayerViewController = VideoPlayerViewController(module: module)
|
||||
videoPlayerViewController.streamUrl = url
|
||||
videoPlayerViewController.fullUrl = fullURL
|
||||
videoPlayerViewController.episodeNumber = selectedEpisodeNumber
|
||||
videoPlayerViewController.episodeImageUrl = selectedEpisodeImage
|
||||
videoPlayerViewController.mediaTitle = title
|
||||
videoPlayerViewController.modalPresentationStyle = .fullScreen
|
||||
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
|
|
|
|||
Loading…
Reference in a new issue