continue watching items added working fine
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run

This commit is contained in:
cranci1 2025-02-14 16:06:55 +01:00
parent 01f90882f9
commit 07ad7b80e5
7 changed files with 182 additions and 3 deletions

View file

@ -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;
};

View 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
}

View 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 []
}
}

View file

@ -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() {

View file

@ -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

View file

@ -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() {

View file

@ -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,