Merge branch 'dev'

This commit is contained in:
cranci1 2025-06-18 17:59:18 +02:00
commit cf5451599b
12 changed files with 43 additions and 49 deletions

View file

@ -183,7 +183,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
watchNextButton, watchNextButton,
volumeSliderHostingView, volumeSliderHostingView,
pipButton, pipButton,
airplayButton airplayButton,
timeBatteryContainer
].compactMap { $0 } ].compactMap { $0 }
private var originalHiddenStates: [UIView: Bool] = [:] private var originalHiddenStates: [UIView: Bool] = [:]
@ -237,13 +238,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
} }
let asset: AVURLAsset let asset: AVURLAsset
// Check if this is a local file URL
if url.scheme == "file" { if url.scheme == "file" {
// For local files, don't add HTTP headers
Logger.shared.log("Loading local file: \(url.absoluteString)", type: "Debug") Logger.shared.log("Loading local file: \(url.absoluteString)", type: "Debug")
// Check if file exists
if FileManager.default.fileExists(atPath: url.path) { if FileManager.default.fileExists(atPath: url.path) {
Logger.shared.log("Local file exists at path: \(url.path)", type: "Debug") Logger.shared.log("Local file exists at path: \(url.path)", type: "Debug")
} else { } else {
@ -252,7 +249,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
asset = AVURLAsset(url: url) asset = AVURLAsset(url: url)
} else { } else {
// For remote URLs, add HTTP headers
Logger.shared.log("Loading remote URL: \(url.absoluteString)", type: "Debug") Logger.shared.log("Loading remote URL: \(url.absoluteString)", type: "Debug")
var request = URLRequest(url: url) var request = URLRequest(url: url)
if let mydict = headers, !mydict.isEmpty { if let mydict = headers, !mydict.isEmpty {
@ -271,8 +267,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
let playerItem = AVPlayerItem(asset: asset) let playerItem = AVPlayerItem(asset: asset)
self.player = AVPlayer(playerItem: playerItem) self.player = AVPlayer(playerItem: playerItem)
// Add error observation
playerItem.addObserver(self, forKeyPath: "status", options: [.new], context: &playerItemKVOContext) playerItem.addObserver(self, forKeyPath: "status", options: [.new], context: &playerItemKVOContext)
Logger.shared.log("Created AVPlayerItem with status: \(playerItem.status.rawValue)", type: "Debug") Logger.shared.log("Created AVPlayerItem with status: \(playerItem.status.rawValue)", type: "Debug")
@ -1357,6 +1351,12 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
airplayButton.setContentCompressionResistancePriority(.required, for: .horizontal) airplayButton.setContentCompressionResistancePriority(.required, for: .horizontal)
controlsContainerView.addSubview(airplayButton) controlsContainerView.addSubview(airplayButton)
airplayButton.layer.shadowColor = UIColor.black.cgColor
airplayButton.layer.shadowOffset = CGSize(width: 0, height: 2)
airplayButton.layer.shadowOpacity = 0.6
airplayButton.layer.shadowRadius = 4
airplayButton.layer.masksToBounds = false
guard AVPictureInPictureController.isPictureInPictureSupported() else { guard AVPictureInPictureController.isPictureInPictureSupported() else {
return return
} }
@ -1368,7 +1368,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
pipController = AVPictureInPictureController(playerLayer: pipPlayerLayer) pipController = AVPictureInPictureController(playerLayer: pipPlayerLayer)
pipController?.delegate = self pipController?.delegate = self
let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium) let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)
let Image = UIImage(systemName: "pip", withConfiguration: config) let Image = UIImage(systemName: "pip", withConfiguration: config)
pipButton = UIButton(type: .system) pipButton = UIButton(type: .system)
@ -1391,7 +1390,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
pipButton.widthAnchor.constraint(equalToConstant: 44), pipButton.widthAnchor.constraint(equalToConstant: 44),
pipButton.heightAnchor.constraint(equalToConstant: 44), pipButton.heightAnchor.constraint(equalToConstant: 44),
airplayButton.centerYAnchor.constraint(equalTo: pipButton.centerYAnchor), airplayButton.centerYAnchor.constraint(equalTo: pipButton.centerYAnchor),
airplayButton.trailingAnchor.constraint(equalTo: pipButton.leadingAnchor, constant: -6), airplayButton.trailingAnchor.constraint(equalTo: pipButton.leadingAnchor, constant: -4),
airplayButton.widthAnchor.constraint(equalToConstant: 44), airplayButton.widthAnchor.constraint(equalToConstant: 44),
airplayButton.heightAnchor.constraint(equalToConstant: 44) airplayButton.heightAnchor.constraint(equalToConstant: 44)
]) ])
@ -1416,11 +1415,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
menuButton.isHidden = true menuButton.isHidden = true
} }
dismissButton.layer.shadowColor = UIColor.black.cgColor menuButton.layer.shadowColor = UIColor.black.cgColor
dismissButton.layer.shadowOffset = CGSize(width: 0, height: 2) menuButton.layer.shadowOffset = CGSize(width: 0, height: 2)
dismissButton.layer.shadowOpacity = 0.6 menuButton.layer.shadowOpacity = 0.6
dismissButton.layer.shadowRadius = 4 menuButton.layer.shadowRadius = 4
dismissButton.layer.masksToBounds = false menuButton.layer.masksToBounds = false
controlsContainerView.addSubview(menuButton) controlsContainerView.addSubview(menuButton)
menuButton.translatesAutoresizingMaskIntoConstraints = false menuButton.translatesAutoresizingMaskIntoConstraints = false
@ -2831,7 +2830,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
} }
private func setupTimeBatteryIndicator() { private func setupTimeBatteryIndicator() {
// Create container
let container = UIView() let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false container.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = .clear container.backgroundColor = .clear
@ -2839,7 +2837,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
controlsContainerView.addSubview(container) controlsContainerView.addSubview(container)
self.timeBatteryContainer = container self.timeBatteryContainer = container
// Create time label
let timeLabel = UILabel() let timeLabel = UILabel()
timeLabel.translatesAutoresizingMaskIntoConstraints = false timeLabel.translatesAutoresizingMaskIntoConstraints = false
timeLabel.textColor = .white timeLabel.textColor = .white
@ -2848,13 +2845,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
container.addSubview(timeLabel) container.addSubview(timeLabel)
self.timeLabel = timeLabel self.timeLabel = timeLabel
// Create separator
let separator = UIView() let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false separator.translatesAutoresizingMaskIntoConstraints = false
separator.backgroundColor = .white.withAlphaComponent(0.5) separator.backgroundColor = .white.withAlphaComponent(0.5)
container.addSubview(separator) container.addSubview(separator)
// Create battery label
let batteryLabel = UILabel() let batteryLabel = UILabel()
batteryLabel.translatesAutoresizingMaskIntoConstraints = false batteryLabel.translatesAutoresizingMaskIntoConstraints = false
batteryLabel.textColor = .white batteryLabel.textColor = .white
@ -2863,7 +2858,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
container.addSubview(batteryLabel) container.addSubview(batteryLabel)
self.batteryLabel = batteryLabel self.batteryLabel = batteryLabel
// Setup constraints
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
container.centerXAnchor.constraint(equalTo: controlsContainerView.centerXAnchor), container.centerXAnchor.constraint(equalTo: controlsContainerView.centerXAnchor),
container.topAnchor.constraint(equalTo: sliderHostingController?.view.bottomAnchor ?? controlsContainerView.bottomAnchor, constant: 2), container.topAnchor.constraint(equalTo: sliderHostingController?.view.bottomAnchor ?? controlsContainerView.bottomAnchor, constant: 2),
@ -2884,19 +2878,14 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
batteryLabel.widthAnchor.constraint(equalToConstant: 50) batteryLabel.widthAnchor.constraint(equalToConstant: 50)
]) ])
// Start time updates
updateTime() updateTime()
timeUpdateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in timeUpdateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.updateTime() self?.updateTime()
} }
// Setup battery monitoring
UIDevice.current.isBatteryMonitoringEnabled = true UIDevice.current.isBatteryMonitoringEnabled = true
updateBatteryLevel() updateBatteryLevel()
NotificationCenter.default.addObserver(self, NotificationCenter.default.addObserver(self, selector: #selector(batteryLevelDidChange), name: UIDevice.batteryLevelDidChangeNotification, object: nil)
selector: #selector(batteryLevelDidChange),
name: UIDevice.batteryLevelDidChangeNotification,
object: nil)
} }
private func updateTime() { private func updateTime() {

View file

@ -208,7 +208,6 @@ class VideoPlayerViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
// Only start normal playback if not launched from SharePlay
if !isLaunchedFromSharePlay { if !isLaunchedFromSharePlay {
player?.play() player?.play()
setInitialPlayerRate() setInitialPlayerRate()
@ -217,7 +216,6 @@ class VideoPlayerViewController: UIViewController {
await checkForFaceTimeAndPromptSharePlay() await checkForFaceTimeAndPromptSharePlay()
} }
} else { } else {
// For SharePlay launches, the playback will be coordinated
setInitialPlayerRate() setInitialPlayerRate()
} }
} }

View file

@ -5,8 +5,8 @@
// Created by Francesco on 05/01/25. // Created by Francesco on 05/01/25.
// //
import Foundation
import Network import Network
import Foundation
class FetchDelegate: NSObject, URLSessionTaskDelegate { class FetchDelegate: NSObject, URLSessionTaskDelegate {
private let allowRedirects: Bool private let allowRedirects: Bool

View file

@ -18,6 +18,13 @@ struct ModuleAdditionSettingsView: View {
@State private var errorMessage: String? @State private var errorMessage: String?
var moduleUrl: String var moduleUrl: String
private var moduleAlreadyExists: Bool {
if let metadata = moduleMetadata {
return moduleManager.modules.contains(where: { $0.metadata.sourceName == metadata.sourceName })
}
return false
}
var body: some View { var body: some View {
ZStack { ZStack {
LinearGradient( LinearGradient(
@ -87,7 +94,7 @@ struct ModuleAdditionSettingsView: View {
.fill(Color(.systemGray5)) .fill(Color(.systemGray5))
} }
} }
.frame(width: 32, height: 32) .frame(width: 40, height: 40)
.clipShape(Circle()) .clipShape(Circle())
.shadow( .shadow(
color: colorScheme == .dark color: colorScheme == .dark
@ -194,10 +201,11 @@ struct ModuleAdditionSettingsView: View {
Button(action: addModule) { Button(action: addModule) {
HStack { HStack {
Image(systemName: "plus.circle.fill") Image(systemName: "plus.circle.fill")
Text("Add Module") .foregroundColor(colorScheme == .dark ? .black : .white)
Text(moduleAlreadyExists ? "Module already added" : "Add Module")
} }
.font(.headline) .font(.headline)
.foregroundColor(Color.accentColor) .foregroundColor(colorScheme == .dark ? .black : .white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 14) .padding(.vertical, 14)
.background( .background(
@ -219,8 +227,8 @@ struct ModuleAdditionSettingsView: View {
) )
.padding(.horizontal, 20) .padding(.horizontal, 20)
} }
.disabled(isLoading || moduleMetadata == nil) .disabled(isLoading || moduleMetadata == nil || moduleAlreadyExists)
.opacity(isLoading ? 0.6 : 1) .opacity(isLoading || moduleAlreadyExists ? 0.6 : 1)
Button(action: { presentationMode.wrappedValue.dismiss() }) { Button(action: { presentationMode.wrappedValue.dismiss() }) {
Text("Cancel") Text("Cancel")

View file

@ -125,7 +125,6 @@ struct AllBookmarks: View {
} }
Button(action: { Button(action: {
if isSelecting { if isSelecting {
// If trash icon tapped
if !selectedBookmarks.isEmpty { if !selectedBookmarks.isEmpty {
for id in selectedBookmarks { for id in selectedBookmarks {
if let item = libraryManager.bookmarks.first(where: { $0.id == id }) { if let item = libraryManager.bookmarks.first(where: { $0.id == id }) {

View file

@ -266,12 +266,17 @@ struct AllWatchingView: View {
UserDefaults.standard.set(99999999.0, forKey: key) UserDefaults.standard.set(99999999.0, forKey: key)
UserDefaults.standard.set(99999999.0, forKey: totalKey) UserDefaults.standard.set(99999999.0, forKey: totalKey)
ContinueWatchingManager.shared.remove(item: item) ContinueWatchingManager.shared.remove(item: item)
loadContinueWatchingItems()
DispatchQueue.main.async {
loadContinueWatchingItems()
}
} }
private func removeItem(item: ContinueWatchingItem) { private func removeItem(item: ContinueWatchingItem) {
ContinueWatchingManager.shared.remove(item: item) ContinueWatchingManager.shared.remove(item: item)
loadContinueWatchingItems() DispatchQueue.main.async {
loadContinueWatchingItems()
}
} }
} }

View file

@ -1508,7 +1508,7 @@ struct MediaInfoView: View {
alert.addAction(UIAlertAction(title: title, style: .default) { _ in alert.addAction(UIAlertAction(title: title, style: .default) { _ in
guard self.activeFetchID == fetchID else { return } guard self.activeFetchID == fetchID else { return }
self.playStream(url: streamUrl, fullURL: href, subtitles: subtitles, headers: headers, fetchID: fetchID) self.playStream(url: streamUrl, fullURL: fullURL, subtitles: subtitles, headers: headers, fetchID: fetchID)
}) })
streamIndex += 1 streamIndex += 1

View file

@ -275,9 +275,6 @@ struct SettingsViewGeneral: View {
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 12) .padding(.vertical, 12)
Divider()
.padding(.horizontal, 16)
List { List {
ForEach(Array(metadataProvidersOrder.enumerated()), id: \.element) { index, provider in ForEach(Array(metadataProvidersOrder.enumerated()), id: \.element) { index, provider in
HStack { HStack {
@ -358,8 +355,6 @@ struct SettingsViewGeneral: View {
} }
} }
.padding(.vertical, 20) .padding(.vertical, 20)
.scrollViewBottomPadding()
.navigationTitle("General")
} }
.navigationTitle(NSLocalizedString("General", comment: "")) .navigationTitle(NSLocalizedString("General", comment: ""))
.scrollViewBottomPadding() .scrollViewBottomPadding()

View file

@ -378,7 +378,7 @@ struct SubtitleSettingsSection: View {
icon: "captions.bubble", icon: "captions.bubble",
title: NSLocalizedString("Enable Subtitles", comment: ""), title: NSLocalizedString("Enable Subtitles", comment: ""),
isOn: $subtitlesEnabled, isOn: $subtitlesEnabled,
showDivider: false showDivider: true
) )
.onChange(of: subtitlesEnabled) { newValue in .onChange(of: subtitlesEnabled) { newValue in
SubtitleSettingsManager.shared.update { settings in SubtitleSettingsManager.shared.update { settings in
@ -454,4 +454,4 @@ struct SubtitleSettingsSection: View {
} }
} }
} }
} }

View file

@ -30,7 +30,7 @@ struct SplashScreenView: View {
isAnimating = true isAnimating = true
} }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.easeOut(duration: 0.5)) { withAnimation(.easeOut(duration: 0.5)) {
showMainApp = true showMainApp = true
} }

View file

@ -247,12 +247,12 @@
path = SearchView; path = SearchView;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0409FE832DFF0870000DB00C /* cz.lproj */ = { 0409FE832DFF0870000DB00C /* cs.lproj */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0409FE822DFF0870000DB00C /* Localizable.strings */, 0409FE822DFF0870000DB00C /* Localizable.strings */,
); );
path = cz.lproj; path = cs.lproj;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0409FE862DFF0870000DB00C /* es.lproj */ = { 0409FE862DFF0870000DB00C /* es.lproj */ = {
@ -632,7 +632,7 @@
0410697D2E00ABE900A157BB /* sk.lproj */, 0410697D2E00ABE900A157BB /* sk.lproj */,
04A1B73B2DFF39EB0064688A /* nn.lproj */, 04A1B73B2DFF39EB0064688A /* nn.lproj */,
0409FE8B2DFF2886000DB00C /* ru.lproj */, 0409FE8B2DFF2886000DB00C /* ru.lproj */,
0409FE832DFF0870000DB00C /* cz.lproj */, 0409FE832DFF0870000DB00C /* cs.lproj */,
0409FE862DFF0870000DB00C /* es.lproj */, 0409FE862DFF0870000DB00C /* es.lproj */,
0488FA9B2DFDF385007575E1 /* ar.lproj */, 0488FA9B2DFDF385007575E1 /* ar.lproj */,
0488FA972DFDF334007575E1 /* fr.lproj */, 0488FA972DFDF334007575E1 /* fr.lproj */,