mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 08:32:00 +00:00
too many things idk
This commit is contained in:
parent
c7c9e5407c
commit
b8d1567efb
10 changed files with 278 additions and 477 deletions
|
|
@ -11,10 +11,6 @@ import Kingfisher
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
HomeView()
|
|
||||||
.tabItem {
|
|
||||||
Label("Home", systemImage: "house")
|
|
||||||
}
|
|
||||||
LibraryView()
|
LibraryView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Library", systemImage: "books.vertical")
|
Label("Library", systemImage: "books.vertical")
|
||||||
|
|
|
||||||
|
|
@ -1,353 +0,0 @@
|
||||||
//
|
|
||||||
// HomeView.swift
|
|
||||||
// Sora
|
|
||||||
//
|
|
||||||
// Created by Francesco on 05/01/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Kingfisher
|
|
||||||
|
|
||||||
struct HomeView: View {
|
|
||||||
@AppStorage("trackingService") private var tracingService: String = "AniList"
|
|
||||||
@State private var aniListItems: [AniListItem] = []
|
|
||||||
@State private var trendingItems: [AniListItem] = []
|
|
||||||
@State private var continueWatchingItems: [ContinueWatchingItem] = []
|
|
||||||
@State private var isLoading: Bool = true
|
|
||||||
|
|
||||||
private var currentDeviceSeasonAndYear: (season: String, year: Int) {
|
|
||||||
let currentDate = Date()
|
|
||||||
let calendar = Calendar.current
|
|
||||||
let year = calendar.component(.year, from: currentDate)
|
|
||||||
let month = calendar.component(.month, from: currentDate)
|
|
||||||
|
|
||||||
let season: String
|
|
||||||
switch month {
|
|
||||||
case 1...3:
|
|
||||||
season = "Winter"
|
|
||||||
case 4...6:
|
|
||||||
season = "Spring"
|
|
||||||
case 7...9:
|
|
||||||
season = "Summer"
|
|
||||||
default:
|
|
||||||
season = "Fall"
|
|
||||||
}
|
|
||||||
return (season, year)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var trendingDateString: String {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateFormat = "EEEE, dd MMMM yyyy"
|
|
||||||
return formatter.string(from: Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
ScrollView {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
if !continueWatchingItems.isEmpty {
|
|
||||||
ContinueWatchingSection(items: $continueWatchingItems) { item in
|
|
||||||
markContinueWatchingItemAsWatched(item: item)
|
|
||||||
} removeItem: { item in
|
|
||||||
removeContinueWatchingItem(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SeasonalSection(
|
|
||||||
title: "Seasonal of \(currentDeviceSeasonAndYear.season) \(String(format: "%d", currentDeviceSeasonAndYear.year))",
|
|
||||||
items: aniListItems,
|
|
||||||
isLoading: isLoading
|
|
||||||
)
|
|
||||||
|
|
||||||
TrendingSection(
|
|
||||||
title: "Trending on \(trendingDateString)",
|
|
||||||
items: trendingItems,
|
|
||||||
isLoading: isLoading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.padding(.bottom, 16)
|
|
||||||
}
|
|
||||||
.navigationTitle("Home")
|
|
||||||
.onAppear {
|
|
||||||
fetchData()
|
|
||||||
}
|
|
||||||
.refreshable {
|
|
||||||
fetchData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetchData() {
|
|
||||||
isLoading = true
|
|
||||||
continueWatchingItems = ContinueWatchingManager.shared.fetchItems()
|
|
||||||
|
|
||||||
let fetchSeasonal: (@escaping ([AniListItem]?) -> Void) -> Void
|
|
||||||
let fetchTrending: (@escaping ([AniListItem]?) -> Void) -> Void
|
|
||||||
|
|
||||||
if tracingService == "TMDB" {
|
|
||||||
fetchSeasonal = TMDBSeasonal.fetchTMDBSeasonal
|
|
||||||
fetchTrending = TMBDTrending.fetchTMDBTrending
|
|
||||||
} else {
|
|
||||||
fetchSeasonal = AnilistServiceSeasonalAnime().fetchSeasonalAnime
|
|
||||||
fetchTrending = AnilistServiceTrendingAnime().fetchTrendingAnime
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchSeasonal { items in
|
|
||||||
aniListItems = items ?? []
|
|
||||||
checkLoadingState()
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchTrending { items in
|
|
||||||
trendingItems = items ?? []
|
|
||||||
checkLoadingState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func checkLoadingState() {
|
|
||||||
if !aniListItems.isEmpty && !trendingItems.isEmpty {
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func markContinueWatchingItemAsWatched(item: ContinueWatchingItem) {
|
|
||||||
let key = "lastPlayedTime_\(item.fullUrl)"
|
|
||||||
let totalKey = "totalTime_\(item.fullUrl)"
|
|
||||||
UserDefaults.standard.set(99999999.0, forKey: key)
|
|
||||||
UserDefaults.standard.set(99999999.0, forKey: totalKey)
|
|
||||||
ContinueWatchingManager.shared.remove(item: item)
|
|
||||||
|
|
||||||
continueWatchingItems.removeAll { $0.id == item.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
private func removeContinueWatchingItem(item: ContinueWatchingItem) {
|
|
||||||
ContinueWatchingManager.shared.remove(item: item)
|
|
||||||
continueWatchingItems.removeAll { $0.id == item.id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContinueWatchingSection: View {
|
|
||||||
@Binding var items: [ContinueWatchingItem]
|
|
||||||
var markAsWatched: (ContinueWatchingItem) -> Void
|
|
||||||
var removeItem: (ContinueWatchingItem) -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
LazyVStack(alignment: .leading) {
|
|
||||||
SectionHeader(title: "Continue Watching")
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
HStack(spacing: 8) {
|
|
||||||
ForEach(Array(items.reversed())) { item in
|
|
||||||
ContinueWatchingCell(item: item) {
|
|
||||||
markAsWatched(item)
|
|
||||||
} removeItem: {
|
|
||||||
removeItem(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
}
|
|
||||||
.frame(height: 190)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContinueWatchingCell: View {
|
|
||||||
let item: ContinueWatchingItem
|
|
||||||
var markAsWatched: () -> Void
|
|
||||||
var removeItem: () -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: {
|
|
||||||
if UserDefaults.standard.string(forKey: "externalPlayer") == "Sora" {
|
|
||||||
let customMediaPlayer = CustomMediaPlayerViewController(
|
|
||||||
module: item.module,
|
|
||||||
urlString: item.streamUrl,
|
|
||||||
fullUrl: item.fullUrl,
|
|
||||||
title: item.mediaTitle,
|
|
||||||
episodeNumber: item.episodeNumber,
|
|
||||||
onWatchNext: { },
|
|
||||||
subtitlesURL: item.subtitles,
|
|
||||||
episodeImageUrl: item.imageUrl
|
|
||||||
)
|
|
||||||
customMediaPlayer.modalPresentationStyle = .fullScreen
|
|
||||||
|
|
||||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
ZStack {
|
|
||||||
KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : 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(
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 240, height: 170)
|
|
||||||
}
|
|
||||||
.contextMenu {
|
|
||||||
Button(action: { markAsWatched() }) {
|
|
||||||
Label("Mark as Watched", systemImage: "checkmark.circle")
|
|
||||||
}
|
|
||||||
Button(role: .destructive, action: { removeItem() }) {
|
|
||||||
Label("Remove Item", systemImage: "trash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SeasonalSection: View {
|
|
||||||
let title: String
|
|
||||||
let items: [AniListItem]
|
|
||||||
let isLoading: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
SectionHeader(title: title)
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
HStack(spacing: 8) {
|
|
||||||
if isLoading {
|
|
||||||
ForEach(0..<5, id: \.self) { _ in
|
|
||||||
HomeSkeletonCell()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ForEach(items, id: \.id) { item in
|
|
||||||
NavigationLink(destination: AniListDetailsView(animeID: item.id)) {
|
|
||||||
AnimeItemCell(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TrendingSection: View {
|
|
||||||
let title: String
|
|
||||||
let items: [AniListItem]
|
|
||||||
let isLoading: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
SectionHeader(title: title)
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
HStack(spacing: 8) {
|
|
||||||
if isLoading {
|
|
||||||
ForEach(0..<5, id: \.self) { _ in
|
|
||||||
HomeSkeletonCell()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ForEach(items, id: \.id) { item in
|
|
||||||
NavigationLink(destination: AniListDetailsView(animeID: item.id)) {
|
|
||||||
AnimeItemCell(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SectionHeader: View {
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Text(title)
|
|
||||||
.font(.headline)
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
.padding(.top, 8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AnimeItemCell: View {
|
|
||||||
let item: AniListItem
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,14 +13,16 @@ struct LibraryItem: Codable, Identifiable {
|
||||||
let imageUrl: String
|
let imageUrl: String
|
||||||
let href: String
|
let href: String
|
||||||
let moduleId: String
|
let moduleId: String
|
||||||
|
let moduleName: String
|
||||||
let dateAdded: Date
|
let dateAdded: Date
|
||||||
|
|
||||||
init(title: String, imageUrl: String, href: String, moduleId: String) {
|
init(title: String, imageUrl: String, href: String, moduleId: String, moduleName: String) {
|
||||||
self.id = UUID()
|
self.id = UUID()
|
||||||
self.title = title
|
self.title = title
|
||||||
self.imageUrl = imageUrl
|
self.imageUrl = imageUrl
|
||||||
self.href = href
|
self.href = href
|
||||||
self.moduleId = moduleId
|
self.moduleId = moduleId
|
||||||
|
self.moduleName = moduleName
|
||||||
self.dateAdded = Date()
|
self.dateAdded = Date()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,15 +57,15 @@ class LibraryManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBookmarked(href: String) -> Bool {
|
func isBookmarked(href: String, moduleName: String) -> Bool {
|
||||||
bookmarks.contains { $0.href == href }
|
bookmarks.contains { $0.href == href }
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleBookmark(title: String, imageUrl: String, href: String, moduleId: String) {
|
func toggleBookmark(title: String, imageUrl: String, href: String, moduleId: String, moduleName: String) {
|
||||||
if let index = bookmarks.firstIndex(where: { $0.href == href }) {
|
if let index = bookmarks.firstIndex(where: { $0.href == href }) {
|
||||||
bookmarks.remove(at: index)
|
bookmarks.remove(at: index)
|
||||||
} else {
|
} else {
|
||||||
let bookmark = LibraryItem(title: title, imageUrl: imageUrl, href: href, moduleId: moduleId)
|
let bookmark = LibraryItem(title: title, imageUrl: imageUrl, href: href, moduleId: moduleId, moduleName: moduleName)
|
||||||
bookmarks.insert(bookmark, at: 0)
|
bookmarks.insert(bookmark, at: 0)
|
||||||
}
|
}
|
||||||
saveBookmarks()
|
saveBookmarks()
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ struct LibraryView: View {
|
||||||
@EnvironmentObject private var libraryManager: LibraryManager
|
@EnvironmentObject private var libraryManager: LibraryManager
|
||||||
@EnvironmentObject private var moduleManager: ModuleManager
|
@EnvironmentObject private var moduleManager: ModuleManager
|
||||||
|
|
||||||
|
@State private var continueWatchingItems: [ContinueWatchingItem] = []
|
||||||
|
|
||||||
private let columns = [
|
private let columns = [
|
||||||
GridItem(.adaptive(minimum: 150), spacing: 16)
|
GridItem(.adaptive(minimum: 150), spacing: 16)
|
||||||
]
|
]
|
||||||
|
|
@ -19,68 +21,252 @@ struct LibraryView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
if libraryManager.bookmarks.isEmpty {
|
VStack(alignment: .leading, spacing: 32) {
|
||||||
VStack(spacing: 8) {
|
Group {
|
||||||
Image(systemName: "magazine")
|
Text("Continue Watching")
|
||||||
.font(.largeTitle)
|
.font(.title2)
|
||||||
.foregroundColor(.secondary)
|
.bold()
|
||||||
Text("No Items saved")
|
.padding(.horizontal, 20)
|
||||||
.font(.headline)
|
|
||||||
Text("You can bookmark items to find them easily here")
|
if continueWatchingItems.isEmpty {
|
||||||
.font(.caption)
|
Text("No items to continue watching")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
} else {
|
||||||
|
ContinueWatchingSection(items: $continueWatchingItems,
|
||||||
|
markAsWatched: { item in
|
||||||
|
markContinueWatchingItemAsWatched(item: item)
|
||||||
|
},
|
||||||
|
removeItem: { item in
|
||||||
|
removeContinueWatchingItem(item: item)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
.frame(maxWidth: .infinity)
|
Group {
|
||||||
} else {
|
Text("Bookmarks")
|
||||||
LazyVGrid(columns: columns, spacing: 16) {
|
.font(.title2)
|
||||||
ForEach(libraryManager.bookmarks) { item in
|
.bold()
|
||||||
if let module = moduleManager.modules.first(where: { $0.id.uuidString == item.moduleId }) {
|
.padding(.horizontal, 20)
|
||||||
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: module)) {
|
|
||||||
VStack {
|
if libraryManager.bookmarks.isEmpty {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
VStack(spacing: 8) {
|
||||||
KFImage(URL(string: item.imageUrl))
|
Image(systemName: "magazine")
|
||||||
.placeholder {
|
.font(.largeTitle)
|
||||||
RoundedRectangle(cornerRadius: 10)
|
.foregroundColor(.secondary)
|
||||||
.fill(Color.gray.opacity(0.3))
|
Text("No Items saved")
|
||||||
|
.font(.headline)
|
||||||
|
Text("Bookmark items for easy access later")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
} else {
|
||||||
|
LazyVGrid(columns: columns, spacing: 16) {
|
||||||
|
ForEach(libraryManager.bookmarks) { item in
|
||||||
|
if let module = moduleManager.modules.first(where: { $0.id.uuidString == item.moduleId }) {
|
||||||
|
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: module)) {
|
||||||
|
VStack {
|
||||||
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
KFImage(URL(string: item.imageUrl))
|
||||||
|
.placeholder {
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(Color.gray.opacity(0.3))
|
||||||
|
.frame(width: 150, height: 225)
|
||||||
|
.shimmering()
|
||||||
|
}
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(2/3, contentMode: .fill)
|
||||||
|
.cornerRadius(10)
|
||||||
.frame(width: 150, height: 225)
|
.frame(width: 150, height: 225)
|
||||||
.shimmering()
|
|
||||||
}
|
KFImage(URL(string: module.metadata.iconUrl))
|
||||||
.resizable()
|
.placeholder {
|
||||||
.aspectRatio(2/3, contentMode: .fill)
|
Circle()
|
||||||
.cornerRadius(10)
|
.fill(Color.gray.opacity(0.3))
|
||||||
.frame(width: 150, height: 225)
|
.frame(width: 35, height: 35)
|
||||||
|
.shimmering()
|
||||||
KFImage(URL(string: module.metadata.iconUrl))
|
}
|
||||||
.placeholder {
|
.resizable()
|
||||||
Circle()
|
.aspectRatio(contentMode: .fit)
|
||||||
.fill(Color.gray.opacity(0.3))
|
|
||||||
.frame(width: 35, height: 35)
|
.frame(width: 35, height: 35)
|
||||||
.shimmering()
|
.clipShape(Circle())
|
||||||
|
.padding(5)
|
||||||
}
|
}
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
Text(item.title)
|
||||||
.frame(width: 35, height: 35)
|
.font(.subheadline)
|
||||||
.clipShape(Circle())
|
.foregroundColor(.primary)
|
||||||
.padding(5)
|
.lineLimit(2)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(item.title)
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.lineLimit(2)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
.padding(.horizontal, 8)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 20)
|
||||||
}
|
}
|
||||||
.navigationTitle("Library")
|
.navigationTitle("Library")
|
||||||
|
.onAppear {
|
||||||
|
fetchContinueWatching()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func fetchContinueWatching() {
|
||||||
|
continueWatchingItems = ContinueWatchingManager.shared.fetchItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func markContinueWatchingItemAsWatched(item: ContinueWatchingItem) {
|
||||||
|
let key = "lastPlayedTime_\(item.fullUrl)"
|
||||||
|
let totalKey = "totalTime_\(item.fullUrl)"
|
||||||
|
UserDefaults.standard.set(99999999.0, forKey: key)
|
||||||
|
UserDefaults.standard.set(99999999.0, forKey: totalKey)
|
||||||
|
ContinueWatchingManager.shared.remove(item: item)
|
||||||
|
continueWatchingItems.removeAll { $0.id == item.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeContinueWatchingItem(item: ContinueWatchingItem) {
|
||||||
|
ContinueWatchingManager.shared.remove(item: item)
|
||||||
|
continueWatchingItems.removeAll { $0.id == item.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContinueWatchingSection and ContinueWatchingCell remain unchanged
|
||||||
|
struct ContinueWatchingSection: View {
|
||||||
|
@Binding var items: [ContinueWatchingItem]
|
||||||
|
var markAsWatched: (ContinueWatchingItem) -> Void
|
||||||
|
var removeItem: (ContinueWatchingItem) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
ForEach(Array(items.reversed())) { item in
|
||||||
|
ContinueWatchingCell(item: item,
|
||||||
|
markAsWatched: {
|
||||||
|
markAsWatched(item)
|
||||||
|
},
|
||||||
|
removeItem: {
|
||||||
|
removeItem(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
}
|
||||||
|
.frame(height: 190)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContinueWatchingCell: View {
|
||||||
|
let item: ContinueWatchingItem
|
||||||
|
var markAsWatched: () -> Void
|
||||||
|
var removeItem: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: {
|
||||||
|
if UserDefaults.standard.string(forKey: "externalPlayer") == "Sora" {
|
||||||
|
let customMediaPlayer = CustomMediaPlayerViewController(
|
||||||
|
module: item.module,
|
||||||
|
urlString: item.streamUrl,
|
||||||
|
fullUrl: item.fullUrl,
|
||||||
|
title: item.mediaTitle,
|
||||||
|
episodeNumber: item.episodeNumber,
|
||||||
|
onWatchNext: { },
|
||||||
|
subtitlesURL: item.subtitles,
|
||||||
|
episodeImageUrl: item.imageUrl
|
||||||
|
)
|
||||||
|
customMediaPlayer.modalPresentationStyle = .fullScreen
|
||||||
|
|
||||||
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
ZStack {
|
||||||
|
KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : 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(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: 240, height: 170)
|
||||||
|
}
|
||||||
|
.contextMenu {
|
||||||
|
Button(action: { markAsWatched() }) {
|
||||||
|
Label("Mark as Watched", systemImage: "checkmark.circle")
|
||||||
|
}
|
||||||
|
Button(role: .destructive, action: { removeItem() }) {
|
||||||
|
Label("Remove Item", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,10 +167,11 @@ struct MediaInfoView: View {
|
||||||
title: title,
|
title: title,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
href: href,
|
href: href,
|
||||||
moduleId: module.id.uuidString
|
moduleId: module.id.uuidString,
|
||||||
|
moduleName: module.metadata.sourceName
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: libraryManager.isBookmarked(href: href) ? "bookmark.fill" : "bookmark")
|
Image(systemName: libraryManager.isBookmarked(href: href, moduleName: module.metadata.sourceName) ? "bookmark.fill" : "bookmark")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 20, height: 27)
|
.frame(width: 20, height: 27)
|
||||||
.foregroundColor(Color.accentColor)
|
.foregroundColor(Color.accentColor)
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ struct SearchView: View {
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack {
|
HStack(spacing: 4) {
|
||||||
if let selectedModule = selectedModule {
|
if let selectedModule = selectedModule {
|
||||||
Text(selectedModule.metadata.sourceName)
|
Text(selectedModule.metadata.sourceName)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
@ -161,6 +161,8 @@ struct SearchView: View {
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.id("moduleMenuHStack")
|
||||||
|
.fixedSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ struct SettingsViewGeneral: View {
|
||||||
@AppStorage("refreshModulesOnLaunch") private var refreshModulesOnLaunch: Bool = false
|
@AppStorage("refreshModulesOnLaunch") private var refreshModulesOnLaunch: Bool = false
|
||||||
@AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata: Bool = true
|
@AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata: Bool = true
|
||||||
@AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false
|
@AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false
|
||||||
|
@AppStorage("metadataProviders") private var metadataProviders: String = "AniList"
|
||||||
|
private let metadataProvidersList = ["AniList", "TMDB"]
|
||||||
@EnvironmentObject var settings: Settings
|
@EnvironmentObject var settings: Settings
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -52,6 +54,20 @@ struct SettingsViewGeneral: View {
|
||||||
}
|
}
|
||||||
Toggle("Fetch Episode metadata", isOn: $fetchEpisodeMetadata)
|
Toggle("Fetch Episode metadata", isOn: $fetchEpisodeMetadata)
|
||||||
.tint(.accentColor)
|
.tint(.accentColor)
|
||||||
|
HStack {
|
||||||
|
Text("Metadata Provider")
|
||||||
|
Spacer()
|
||||||
|
Menu(metadataProviders) {
|
||||||
|
ForEach(metadataProvidersList, id: \.self) { provider in
|
||||||
|
Button(action: {
|
||||||
|
metadataProviders = provider
|
||||||
|
}) {
|
||||||
|
Text(provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Modules"), footer: Text("Note that the modules will be replaced only if there is a different version string inside the JSON file.")) {
|
Section(header: Text("Modules"), footer: Text("Note that the modules will be replaced only if there is a different version string inside the JSON file.")) {
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
//
|
|
||||||
// SettingsViewTrackingServices.swift
|
|
||||||
// Sulfur
|
|
||||||
//
|
|
||||||
// Created by Francesco on 05/03/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Kingfisher
|
|
||||||
|
|
||||||
struct SettingsViewTrackingServices: View {
|
|
||||||
@AppStorage("trackingService") private var trackingService: String = "AniList"
|
|
||||||
@EnvironmentObject var settings: Settings
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Form {
|
|
||||||
Section(header: Text("Tracking Service")) {
|
|
||||||
HStack {
|
|
||||||
Text("Service")
|
|
||||||
Spacer()
|
|
||||||
Menu {
|
|
||||||
Button(action: { trackingService = "AniList" }) {
|
|
||||||
HStack {
|
|
||||||
KFImage(URL(string: "https://avatars.githubusercontent.com/u/18018524?s=280&v=4"))
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
Text("AniList")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button(action: { trackingService = "TMDB" }) {
|
|
||||||
HStack {
|
|
||||||
KFImage(URL(string: "https://pbs.twimg.com/profile_images/1243623122089041920/gVZIvphd_400x400.jpg"))
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
Text("TMDB")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
HStack {
|
|
||||||
KFImage(URL(string: trackingService == "TMDB" ? "https://pbs.twimg.com/profile_images/1243623122089041920/gVZIvphd_400x400.jpg" : "https://avatars.githubusercontent.com/u/18018524?s=280&v=4"))
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
Text(trackingService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Tracking Service")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,9 +21,6 @@ struct SettingsView: View {
|
||||||
NavigationLink(destination: SettingsViewModule()) {
|
NavigationLink(destination: SettingsViewModule()) {
|
||||||
Text("Modules")
|
Text("Modules")
|
||||||
}
|
}
|
||||||
NavigationLink(destination: SettingsViewTrackingServices()) {
|
|
||||||
Text("Tracking Services")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Info")) {
|
Section(header: Text("Info")) {
|
||||||
|
|
@ -49,6 +46,19 @@ struct SettingsView: View {
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Button(action: {
|
||||||
|
if let url = URL(string: "https://discord.gg/x7hppDWFDZ") {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Join the Discord")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "safari")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if let url = URL(string: "https://github.com/cranci1/Sora/issues") {
|
if let url = URL(string: "https://github.com/cranci1/Sora/issues") {
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
||||||
|
|
@ -63,12 +73,12 @@ struct SettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if let url = URL(string: "https://discord.gg/x7hppDWFDZ") {
|
if let url = URL(string: "https://github.com/cranci1/Sora/blob/dev/LICENSE") {
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Join the Discord")
|
Text("License (GPLv3.0)")
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
Spacer()
|
Spacer()
|
||||||
Image(systemName: "safari")
|
Image(systemName: "safari")
|
||||||
|
|
@ -76,7 +86,7 @@ struct SettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section(footer: Text("Running Sora 0.2.1")) {}
|
Section(footer: Text("Running Sora 0.2.1 - cranci1")) {}
|
||||||
}
|
}
|
||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,11 @@
|
||||||
1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */; };
|
1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */; };
|
||||||
1334FF522D7871B7007E289F /* TMDBItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF512D7871B7007E289F /* TMDBItem.swift */; };
|
1334FF522D7871B7007E289F /* TMDBItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF512D7871B7007E289F /* TMDBItem.swift */; };
|
||||||
1334FF542D787217007E289F /* TMDBRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF532D787217007E289F /* TMDBRequest.swift */; };
|
1334FF542D787217007E289F /* TMDBRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF532D787217007E289F /* TMDBRequest.swift */; };
|
||||||
1334FF562D7872E9007E289F /* SettingsViewTrackingServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF552D7872E9007E289F /* SettingsViewTrackingServices.swift */; };
|
|
||||||
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6D2D2BE2500075467E /* SoraApp.swift */; };
|
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6D2D2BE2500075467E /* SoraApp.swift */; };
|
||||||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; };
|
133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; };
|
||||||
133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; };
|
133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; };
|
||||||
133D7C752D2BE2520075467E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C742D2BE2520075467E /* Preview Assets.xcassets */; };
|
133D7C752D2BE2520075467E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C742D2BE2520075467E /* Preview Assets.xcassets */; };
|
||||||
133D7C8C2D2BE2640075467E /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C7C2D2BE2630075467E /* SearchView.swift */; };
|
133D7C8C2D2BE2640075467E /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C7C2D2BE2630075467E /* SearchView.swift */; };
|
||||||
133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C7D2D2BE2630075467E /* HomeView.swift */; };
|
|
||||||
133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C7E2D2BE2630075467E /* LibraryView.swift */; };
|
133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C7E2D2BE2630075467E /* LibraryView.swift */; };
|
||||||
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C802D2BE2630075467E /* MediaInfoView.swift */; };
|
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C802D2BE2630075467E /* MediaInfoView.swift */; };
|
||||||
133D7C902D2BE2640075467E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C822D2BE2630075467E /* SettingsView.swift */; };
|
133D7C902D2BE2640075467E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C822D2BE2630075467E /* SettingsView.swift */; };
|
||||||
|
|
@ -77,14 +75,12 @@
|
||||||
1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TMDB-Trending.swift"; sourceTree = "<group>"; };
|
1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TMDB-Trending.swift"; sourceTree = "<group>"; };
|
||||||
1334FF512D7871B7007E289F /* TMDBItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TMDBItem.swift; sourceTree = "<group>"; };
|
1334FF512D7871B7007E289F /* TMDBItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TMDBItem.swift; sourceTree = "<group>"; };
|
||||||
1334FF532D787217007E289F /* TMDBRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TMDBRequest.swift; sourceTree = "<group>"; };
|
1334FF532D787217007E289F /* TMDBRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TMDBRequest.swift; sourceTree = "<group>"; };
|
||||||
1334FF552D7872E9007E289F /* SettingsViewTrackingServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTrackingServices.swift; sourceTree = "<group>"; };
|
|
||||||
133D7C6A2D2BE2500075467E /* Sulfur.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sulfur.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
133D7C6A2D2BE2500075467E /* Sulfur.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sulfur.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
133D7C6D2D2BE2500075467E /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = "<group>"; };
|
133D7C6D2D2BE2500075467E /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = "<group>"; };
|
||||||
133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
133D7C712D2BE2520075467E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
133D7C712D2BE2520075467E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
133D7C742D2BE2520075467E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
133D7C742D2BE2520075467E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
133D7C7C2D2BE2630075467E /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
133D7C7C2D2BE2630075467E /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||||
133D7C7D2D2BE2630075467E /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
|
||||||
133D7C7E2D2BE2630075467E /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
133D7C7E2D2BE2630075467E /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
||||||
133D7C802D2BE2630075467E /* MediaInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaInfoView.swift; sourceTree = "<group>"; };
|
133D7C802D2BE2630075467E /* MediaInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaInfoView.swift; sourceTree = "<group>"; };
|
||||||
133D7C822D2BE2630075467E /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
133D7C822D2BE2630075467E /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -260,7 +256,6 @@
|
||||||
1399FAD22D3AB34F00E97C31 /* SettingsView */,
|
1399FAD22D3AB34F00E97C31 /* SettingsView */,
|
||||||
133F55B92D33B53E00E08EEA /* LibraryView */,
|
133F55B92D33B53E00E08EEA /* LibraryView */,
|
||||||
133D7C7C2D2BE2630075467E /* SearchView.swift */,
|
133D7C7C2D2BE2630075467E /* SearchView.swift */,
|
||||||
133D7C7D2D2BE2630075467E /* HomeView.swift */,
|
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -283,7 +278,6 @@
|
||||||
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */,
|
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */,
|
||||||
135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */,
|
135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */,
|
||||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */,
|
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */,
|
||||||
1334FF552D7872E9007E289F /* SettingsViewTrackingServices.swift */,
|
|
||||||
);
|
);
|
||||||
path = SettingsSubViews;
|
path = SettingsSubViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -525,7 +519,6 @@
|
||||||
1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */,
|
1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */,
|
||||||
13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */,
|
13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */,
|
||||||
133D7C932D2BE2640075467E /* Modules.swift in Sources */,
|
133D7C932D2BE2640075467E /* Modules.swift in Sources */,
|
||||||
1334FF562D7872E9007E289F /* SettingsViewTrackingServices.swift in Sources */,
|
|
||||||
136F21B92D5B8DD8006409AC /* AniList-MediaInfo.swift in Sources */,
|
136F21B92D5B8DD8006409AC /* AniList-MediaInfo.swift in Sources */,
|
||||||
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */,
|
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */,
|
||||||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
||||||
|
|
@ -539,7 +532,6 @@
|
||||||
13103E892D58A39A000F0673 /* AniListItem.swift in Sources */,
|
13103E892D58A39A000F0673 /* AniListItem.swift in Sources */,
|
||||||
131845F92D47C62D00CA7A54 /* SettingsViewGeneral.swift in Sources */,
|
131845F92D47C62D00CA7A54 /* SettingsViewGeneral.swift in Sources */,
|
||||||
13103E8E2D58E04A000F0673 /* SkeletonCell.swift in Sources */,
|
13103E8E2D58E04A000F0673 /* SkeletonCell.swift in Sources */,
|
||||||
133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */,
|
|
||||||
13D842552D45267500EBBFA6 /* DropManager.swift in Sources */,
|
13D842552D45267500EBBFA6 /* DropManager.swift in Sources */,
|
||||||
13103E8B2D58E028000F0673 /* View.swift in Sources */,
|
13103E8B2D58E028000F0673 /* View.swift in Sources */,
|
||||||
1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */,
|
1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue