mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-18 23:22:08 +00:00
Small hotfixes (#29)
This commit is contained in:
commit
087d657ad9
8 changed files with 314 additions and 238 deletions
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Build and Release IPA
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
build:
|
||||
name: Build IPA
|
||||
|
|
@ -24,6 +24,6 @@ jobs:
|
|||
- name: Upload IPA artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Sora-IPA
|
||||
path: build/Sora.ipa
|
||||
name: Sulfur-IPA
|
||||
path: build/Sulfur.ipa
|
||||
compression-level: 0
|
||||
|
|
|
|||
27
Sora/Utils/Extensions/finTopView.swift
Normal file
27
Sora/Utils/Extensions/finTopView.swift
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// finTopView.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 04/03/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class findTopViewController {
|
||||
static func findViewController(_ viewController: UIViewController) -> UIViewController {
|
||||
if let presented = viewController.presentedViewController {
|
||||
return findViewController(presented)
|
||||
}
|
||||
|
||||
if let navigationController = viewController as? UINavigationController {
|
||||
return findViewController(navigationController.visibleViewController ?? navigationController)
|
||||
}
|
||||
|
||||
if let tabBarController = viewController as? UITabBarController,
|
||||
let selected = tabBarController.selectedViewController {
|
||||
return findViewController(selected)
|
||||
}
|
||||
|
||||
return viewController
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,10 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
var sliderViewModel = SliderViewModel()
|
||||
var isSliderEditing = false
|
||||
|
||||
var watchNextButtonNormalConstraints: [NSLayoutConstraint] = []
|
||||
var watchNextButtonControlsConstraints: [NSLayoutConstraint] = []
|
||||
var isControlsVisible = false
|
||||
|
||||
init(module: ScrapingModule,
|
||||
urlString: String,
|
||||
fullUrl: String,
|
||||
|
|
@ -106,8 +110,9 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
setupControls()
|
||||
setupSubtitleLabel()
|
||||
setupDismissButton()
|
||||
setupSpeedButton()
|
||||
setupMenuButton()
|
||||
setupSpeedButton()
|
||||
setupWatchNextButton()
|
||||
addTimeObserver()
|
||||
startUpdateTimer()
|
||||
setupAudioSession()
|
||||
|
|
@ -244,17 +249,6 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
sliderHostView.heightAnchor.constraint(equalToConstant: 30)
|
||||
])
|
||||
|
||||
watchNextButton = UIButton(type: .system)
|
||||
watchNextButton.setTitle("Watch Next", for: .normal)
|
||||
watchNextButton.setImage(UIImage(systemName: "forward.fill"), for: .normal)
|
||||
watchNextButton.backgroundColor = .white
|
||||
watchNextButton.layer.cornerRadius = 16
|
||||
watchNextButton.setTitleColor(.black, for: .normal)
|
||||
watchNextButton.addTarget(self, action: #selector(watchNextTapped), for: .touchUpInside)
|
||||
watchNextButton.isHidden = true
|
||||
controlsContainerView.addSubview(watchNextButton)
|
||||
watchNextButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
playPauseButton.centerXAnchor.constraint(equalTo: controlsContainerView.centerXAnchor),
|
||||
playPauseButton.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor),
|
||||
|
|
@ -269,12 +263,7 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
forwardButton.centerYAnchor.constraint(equalTo: playPauseButton.centerYAnchor),
|
||||
forwardButton.leadingAnchor.constraint(equalTo: playPauseButton.trailingAnchor, constant: 30),
|
||||
forwardButton.widthAnchor.constraint(equalToConstant: 40),
|
||||
forwardButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
|
||||
watchNextButton.trailingAnchor.constraint(equalTo: controlsContainerView.trailingAnchor, constant: -10),
|
||||
watchNextButton.bottomAnchor.constraint(equalTo: controlsContainerView.bottomAnchor, constant: -80),
|
||||
watchNextButton.heightAnchor.constraint(equalToConstant: 50),
|
||||
watchNextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||
forwardButton.heightAnchor.constraint(equalToConstant: 40)
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -288,9 +277,9 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
subtitleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
subtitleLabel.bottomAnchor.constraint(equalTo: sliderHostingController?.view.topAnchor ?? view.safeAreaLayoutGuide.bottomAnchor),
|
||||
subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 20),
|
||||
subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -20)
|
||||
subtitleLabel.bottomAnchor.constraint(equalTo: sliderHostingController?.view.bottomAnchor ?? view.safeAreaLayoutGuide.bottomAnchor),
|
||||
subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 36),
|
||||
subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -36)
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -323,9 +312,10 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
|
||||
controlsContainerView.addSubview(menuButton)
|
||||
menuButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
guard let sliderView = sliderHostingController?.view else { return }
|
||||
NSLayoutConstraint.activate([
|
||||
menuButton.bottomAnchor.constraint(equalTo: controlsContainerView.bottomAnchor, constant: -50),
|
||||
menuButton.trailingAnchor.constraint(equalTo: speedButton.leadingAnchor),
|
||||
menuButton.bottomAnchor.constraint(equalTo: sliderView.topAnchor),
|
||||
menuButton.trailingAnchor.constraint(equalTo: sliderView.trailingAnchor),
|
||||
menuButton.widthAnchor.constraint(equalToConstant: 40),
|
||||
menuButton.heightAnchor.constraint(equalToConstant: 40)
|
||||
])
|
||||
|
|
@ -341,15 +331,46 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
|
||||
controlsContainerView.addSubview(speedButton)
|
||||
speedButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
guard let sliderView = sliderHostingController?.view else { return }
|
||||
NSLayoutConstraint.activate([
|
||||
speedButton.bottomAnchor.constraint(equalTo: sliderView.topAnchor),
|
||||
speedButton.trailingAnchor.constraint(equalTo: sliderView.trailingAnchor),
|
||||
speedButton.bottomAnchor.constraint(equalTo: controlsContainerView.bottomAnchor, constant: -50),
|
||||
speedButton.trailingAnchor.constraint(equalTo: menuButton.leadingAnchor),
|
||||
speedButton.widthAnchor.constraint(equalToConstant: 40),
|
||||
speedButton.heightAnchor.constraint(equalToConstant: 40)
|
||||
])
|
||||
}
|
||||
|
||||
func setupWatchNextButton() {
|
||||
watchNextButton = UIButton(type: .system)
|
||||
watchNextButton.setTitle("Watch Next", for: .normal)
|
||||
watchNextButton.setImage(UIImage(systemName: "forward.fill"), for: .normal)
|
||||
watchNextButton.tintColor = .black
|
||||
watchNextButton.backgroundColor = .white
|
||||
watchNextButton.layer.cornerRadius = 25
|
||||
watchNextButton.setTitleColor(.black, for: .normal)
|
||||
watchNextButton.addTarget(self, action: #selector(watchNextTapped), for: .touchUpInside)
|
||||
watchNextButton.isHidden = true
|
||||
watchNextButton.alpha = 0.8
|
||||
|
||||
view.addSubview(watchNextButton)
|
||||
watchNextButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
watchNextButtonNormalConstraints = [
|
||||
watchNextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
|
||||
watchNextButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40),
|
||||
watchNextButton.heightAnchor.constraint(equalToConstant: 50),
|
||||
watchNextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||
]
|
||||
|
||||
watchNextButtonControlsConstraints = [
|
||||
watchNextButton.trailingAnchor.constraint(equalTo: speedButton.leadingAnchor),
|
||||
watchNextButton.bottomAnchor.constraint(equalTo: speedButton.bottomAnchor, constant: -5),
|
||||
watchNextButton.heightAnchor.constraint(equalToConstant: 50),
|
||||
watchNextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(watchNextButtonControlsConstraints)
|
||||
}
|
||||
|
||||
func updateSubtitleLabelAppearance() {
|
||||
subtitleLabel.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize))
|
||||
subtitleLabel.textColor = subtitleUIColor()
|
||||
|
|
@ -405,7 +426,6 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
&& self.duration != 0 {
|
||||
|
||||
if UserDefaults.standard.bool(forKey: "hideNextButton") {
|
||||
self.watchNextButton.isHidden = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
||||
self.watchNextButton.isHidden = true
|
||||
}
|
||||
|
|
@ -444,8 +464,22 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
}
|
||||
|
||||
@objc func toggleControls() {
|
||||
isControlsVisible.toggle()
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.controlsContainerView.alpha = self.controlsContainerView.alpha == 0 ? 1 : 0
|
||||
self.controlsContainerView.alpha = self.isControlsVisible ? 1 : 0
|
||||
|
||||
if self.isControlsVisible {
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints)
|
||||
self.watchNextButton.alpha = 1.0
|
||||
} else {
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints)
|
||||
self.watchNextButton.alpha = 0.8
|
||||
}
|
||||
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -148,4 +148,11 @@ class VideoPlayerViewController: UIViewController {
|
|||
override var prefersStatusBarHidden: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
deinit {
|
||||
player?.pause()
|
||||
if let timeObserverToken = timeObserverToken {
|
||||
player?.removeTimeObserver(timeObserverToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,229 +41,231 @@ struct HomeView: View {
|
|||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
if !continueWatchingItems.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Continue Watching")
|
||||
.font(.headline)
|
||||
.padding(.horizontal, 8)
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(Array(continueWatchingItems.reversed())) { item in
|
||||
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 {
|
||||
rootVC.present(customMediaPlayer, animated: true, completion: nil)
|
||||
VStack {
|
||||
ScrollView {
|
||||
if !continueWatchingItems.isEmpty {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Text("Continue Watching")
|
||||
.font(.headline)
|
||||
.padding(.horizontal, 8)
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(Array(continueWatchingItems.reversed())) { item in
|
||||
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)
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
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)
|
||||
}
|
||||
.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 {
|
||||
KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
.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 {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: 240, height: 135)
|
||||
.frame(width: 130, height: 195)
|
||||
.shimmering()
|
||||
}
|
||||
.setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
|
||||
.resizable()
|
||||
.aspectRatio(16/9, contentMode: .fill)
|
||||
.frame(width: 240, height: 135)
|
||||
.scaledToFill()
|
||||
.frame(width: 130, height: 195)
|
||||
.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)
|
||||
Text(item.title.romaji)
|
||||
.font(.caption)
|
||||
.lineLimit(2)
|
||||
.frame(width: 130)
|
||||
.lineLimit(1)
|
||||
.multilineTextAlignment(.center)
|
||||
.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)
|
||||
}
|
||||
.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 {
|
||||
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)
|
||||
|
||||
HStack(alignment: .bottom, spacing: 5) {
|
||||
Text("Trending")
|
||||
.font(.headline)
|
||||
Text("on \(trendingDateString)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
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(.horizontal, 8)
|
||||
}
|
||||
|
||||
HStack(alignment: .bottom, spacing: 5) {
|
||||
Text("Trending")
|
||||
.font(.headline)
|
||||
Text("on \(trendingDateString)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
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)
|
||||
}
|
||||
.navigationTitle("Home")
|
||||
}
|
||||
.onAppear {
|
||||
continueWatchingItems = ContinueWatchingManager.shared.fetchItems()
|
||||
AnilistServiceSeasonalAnime().fetchSeasonalAnime { items in
|
||||
if let items = items {
|
||||
aniListItems = items
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.navigationTitle("Home")
|
||||
}
|
||||
.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
|
||||
AnilistServiceTrendingAnime().fetchTrendingAnime { items in
|
||||
if let items = items {
|
||||
trendingItems = items
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -519,7 +519,7 @@ struct MediaInfoView: View {
|
|||
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let rootVC = windowScene.windows.first?.rootViewController {
|
||||
rootVC.present(customMediaPlayer, animated: true, completion: nil)
|
||||
findTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil)
|
||||
}
|
||||
return
|
||||
default:
|
||||
|
|
@ -541,7 +541,7 @@ struct MediaInfoView: View {
|
|||
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let rootVC = windowScene.windows.first?.rootViewController {
|
||||
rootVC.present(videoPlayerViewController, animated: true, completion: nil)
|
||||
findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; };
|
||||
133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; };
|
||||
133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; };
|
||||
1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; };
|
||||
1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 1359ED192D76FA7D00C13034 /* Drops */; };
|
||||
135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; };
|
||||
136F21B92D5B8DD8006409AC /* AniList-MediaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F21B82D5B8DD8006409AC /* AniList-MediaInfo.swift */; };
|
||||
136F21BC2D5B8F29006409AC /* AniList-DetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F21BB2D5B8F29006409AC /* AniList-DetailsView.swift */; };
|
||||
|
|
@ -44,7 +46,6 @@
|
|||
13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */; };
|
||||
13CBA0882D60F19C00EFE70A /* VTTSubtitlesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CBA0872D60F19C00EFE70A /* VTTSubtitlesLoader.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 */; };
|
||||
13D99CF72D4E73C300250A86 /* ModuleAdditionSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D99CF62D4E73C300250A86 /* ModuleAdditionSettingsView.swift */; };
|
||||
13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */; };
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
133D7C892D2BE2640075467E /* Modules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; };
|
||||
133D7C8B2D2BE2640075467E /* JSController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = "<group>"; };
|
||||
133F55BA2D33B55100E08EEA /* LibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = "<group>"; };
|
||||
1359ED132D76F49900C13034 /* finTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = finTopView.swift; sourceTree = "<group>"; };
|
||||
135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewPlayer.swift; sourceTree = "<group>"; };
|
||||
136F21B82D5B8DD8006409AC /* AniList-MediaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AniList-MediaInfo.swift"; sourceTree = "<group>"; };
|
||||
136F21BB2D5B8F29006409AC /* AniList-DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AniList-DetailsView.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -110,7 +112,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13D842522D4523B800EBBFA6 /* Drops in Frameworks */,
|
||||
1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */,
|
||||
133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -266,6 +268,7 @@
|
|||
children = (
|
||||
1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */,
|
||||
133D7C872D2BE2640075467E /* URLSession.swift */,
|
||||
1359ED132D76F49900C13034 /* finTopView.swift */,
|
||||
13CBEFD92D5F7D1200D011EE /* String.swift */,
|
||||
13103E8A2D58E028000F0673 /* View.swift */,
|
||||
);
|
||||
|
|
@ -405,7 +408,7 @@
|
|||
name = Sulfur;
|
||||
packageProductDependencies = (
|
||||
133D7C962D2BE2AF0075467E /* Kingfisher */,
|
||||
13D842512D4523B800EBBFA6 /* Drops */,
|
||||
1359ED192D76FA7D00C13034 /* Drops */,
|
||||
);
|
||||
productName = Sora;
|
||||
productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */;
|
||||
|
|
@ -436,7 +439,7 @@
|
|||
mainGroup = 133D7C612D2BE2500075467E;
|
||||
packageReferences = (
|
||||
133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
13D842502D4523B800EBBFA6 /* XCRemoteSwiftPackageReference "Drops" */,
|
||||
1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */,
|
||||
);
|
||||
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -467,6 +470,7 @@
|
|||
135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */,
|
||||
1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */,
|
||||
13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */,
|
||||
1359ED142D76F49900C13034 /* finTopView.swift in Sources */,
|
||||
1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */,
|
||||
13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */,
|
||||
139935662D468C450065CEFF /* ModuleManager.swift in Sources */,
|
||||
|
|
@ -646,6 +650,7 @@
|
|||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Sora/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Sora;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
|
|
@ -686,6 +691,7 @@
|
|||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Sora/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Sora;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
|
|
@ -742,12 +748,12 @@
|
|||
version = 7.9.1;
|
||||
};
|
||||
};
|
||||
13D842502D4523B800EBBFA6 /* XCRemoteSwiftPackageReference "Drops" */ = {
|
||||
1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/omaralbeik/Drops.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
|
@ -758,9 +764,9 @@
|
|||
package = 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = Kingfisher;
|
||||
};
|
||||
13D842512D4523B800EBBFA6 /* Drops */ = {
|
||||
1359ED192D76FA7D00C13034 /* Drops */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 13D842502D4523B800EBBFA6 /* XCRemoteSwiftPackageReference "Drops" */;
|
||||
package = 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */;
|
||||
productName = Drops;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
"package": "Drops",
|
||||
"repositoryURL": "https://github.com/omaralbeik/Drops.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "a183ee6f79f21c940092a19c2cba756555422371",
|
||||
"version": "1.7.0"
|
||||
"branch": "main",
|
||||
"revision": "5824681795286c36bdc4a493081a63e64e2a064e",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue