mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 16:42:01 +00:00
Tv os (#119)
* First tvOS commit Barely usable on tvOS: - changed documentDirectory to cachesDirectory because is the only writeable folder - changed default player on default because custom has too many issues to fix on tvOS - commented a lot of lines because that property or function does not exist on tvOS - to add modules copy the link from Iphone * add conditional compilation Only searchview need a separation of a class due to compilation timeout issue * Fix incompatibility on tvOS --------- Co-authored-by: K <kimiko88@users.noreply.github.com>
This commit is contained in:
parent
713046ce64
commit
28533651f0
21 changed files with 408 additions and 66 deletions
|
|
@ -14,6 +14,7 @@ class DropManager {
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
func showDrop(title: String, subtitle: String, duration: TimeInterval, icon: UIImage?) {
|
func showDrop(title: String, subtitle: String, duration: TimeInterval, icon: UIImage?) {
|
||||||
|
#if !os(tvOS)
|
||||||
let position: Drop.Position = .top
|
let position: Drop.Position = .top
|
||||||
|
|
||||||
let drop = Drop(
|
let drop = Drop(
|
||||||
|
|
@ -24,5 +25,6 @@ class DropManager {
|
||||||
duration: .seconds(duration)
|
duration: .seconds(duration)
|
||||||
)
|
)
|
||||||
Drops.show(drop)
|
Drops.show(drop)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public extension UIDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity
|
func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity
|
||||||
#if os(iOS)
|
#if !os(tvOS)
|
||||||
switch identifier {
|
switch identifier {
|
||||||
case "iPod5,1":
|
case "iPod5,1":
|
||||||
return "iPod touch (5th generation)"
|
return "iPod touch (5th generation)"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,12 @@ class Logger {
|
||||||
private let logFilterViewModel = LogFilterViewModel.shared
|
private let logFilterViewModel = LogFilterViewModel.shared
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
#if !os(tvOS)
|
||||||
|
let directory = FileManager.SearchPathDirectory.documentDirectory
|
||||||
|
#elseif os(tvOS)
|
||||||
|
let directory = FileManager.SearchPathDirectory.cachesDirectory
|
||||||
|
#endif
|
||||||
|
let documentDirectory = FileManager.default.urls(for: directory, in: .userDomainMask).first!
|
||||||
logFileURL = documentDirectory.appendingPathComponent("logs.txt")
|
logFileURL = documentDirectory.appendingPathComponent("logs.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
|
||||||
}
|
}
|
||||||
.frame(width: bounds.size.width, height: bounds.size.height, alignment: .center)
|
.frame(width: bounds.size.width, height: bounds.size.height, alignment: .center)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
#if !os(tvOS)
|
||||||
.gesture(
|
.gesture(
|
||||||
DragGesture(minimumDistance: 0, coordinateSpace: .local)
|
DragGesture(minimumDistance: 0, coordinateSpace: .local)
|
||||||
.updating($isActive) { _, state, _ in
|
.updating($isActive) { _, state, _ in
|
||||||
|
|
@ -108,6 +109,7 @@ struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
|
||||||
localTempProgress = 0
|
localTempProgress = 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
#endif
|
||||||
.onChange(of: isActive) { newValue in
|
.onChange(of: isActive) { newValue in
|
||||||
value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound)
|
value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound)
|
||||||
onEditingChanged(newValue)
|
onEditingChanged(newValue)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ struct VolumeSlider<T: BinaryFloatingPoint>: View {
|
||||||
.animation(animation, value: isActive)
|
.animation(animation, value: isActive)
|
||||||
}
|
}
|
||||||
.frame(width: bounds.size.width, height: bounds.size.height)
|
.frame(width: bounds.size.width, height: bounds.size.height)
|
||||||
|
#if !os(tvOS)
|
||||||
.gesture(
|
.gesture(
|
||||||
DragGesture(minimumDistance: 0, coordinateSpace: .local)
|
DragGesture(minimumDistance: 0, coordinateSpace: .local)
|
||||||
.updating($isActive) { _, state, _ in state = true }
|
.updating($isActive) { _, state, _ in state = true }
|
||||||
|
|
@ -68,6 +69,7 @@ struct VolumeSlider<T: BinaryFloatingPoint>: View {
|
||||||
localTempProgress = 0
|
localTempProgress = 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
#endif
|
||||||
.onChange(of: isActive) { newValue in
|
.onChange(of: isActive) { newValue in
|
||||||
if !newValue {
|
if !newValue {
|
||||||
value = sliderValueInRange()
|
value = sliderValueInRange()
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
private var volumeObserver: NSKeyValueObservation?
|
private var volumeObserver: NSKeyValueObservation?
|
||||||
private var audioSession = AVAudioSession.sharedInstance()
|
private var audioSession = AVAudioSession.sharedInstance()
|
||||||
private var hiddenVolumeView = MPVolumeView(frame: .zero)
|
private var hiddenVolumeView = MPVolumeView(frame: .zero)
|
||||||
|
#if !os(tvOS)
|
||||||
private var systemVolumeSlider: UISlider?
|
private var systemVolumeSlider: UISlider?
|
||||||
|
#endif
|
||||||
private var volumeValue: Double = 0.0
|
private var volumeValue: Double = 0.0
|
||||||
private var volumeViewModel = VolumeViewModel()
|
private var volumeViewModel = VolumeViewModel()
|
||||||
var volumeSliderHostingView: UIView?
|
var volumeSliderHostingView: UIView?
|
||||||
|
|
@ -281,14 +283,16 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
playerViewController.allowsVideoFrameAnalysis = false
|
playerViewController.allowsVideoFrameAnalysis = false
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if let url = subtitlesURL, !url.isEmpty {
|
if let url = subtitlesURL, !url.isEmpty {
|
||||||
subtitlesLoader.load(from: url)
|
subtitlesLoader.load(from: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isControlsVisible = true
|
self.isControlsVisible = true
|
||||||
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
||||||
|
|
@ -297,7 +301,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
self.view.layoutIfNeeded()
|
self.view.layoutIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
hiddenVolumeView.showsRouteButton = false
|
hiddenVolumeView.showsRouteButton = false
|
||||||
|
#endif
|
||||||
hiddenVolumeView.isHidden = true
|
hiddenVolumeView.isHidden = true
|
||||||
view.addSubview(hiddenVolumeView)
|
view.addSubview(hiddenVolumeView)
|
||||||
|
|
||||||
|
|
@ -307,9 +313,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
hiddenVolumeView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
hiddenVolumeView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||||
hiddenVolumeView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
hiddenVolumeView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
if let slider = hiddenVolumeView.subviews.first(where: { $0 is UISlider }) as? UISlider {
|
if let slider = hiddenVolumeView.subviews.first(where: { $0 is UISlider }) as? UISlider {
|
||||||
systemVolumeSlider = slider
|
systemVolumeSlider = slider
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
|
@ -397,10 +405,17 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getSegmentsColor() -> Color {
|
private func getSegmentsColor() -> Color {
|
||||||
|
#if !os(tvOS)
|
||||||
if let data = UserDefaults.standard.data(forKey: "segmentsColorData"),
|
if let data = UserDefaults.standard.data(forKey: "segmentsColorData"),
|
||||||
let uiColor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor {
|
let uiColor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor {
|
||||||
return Color(uiColor)
|
return Color(uiColor)
|
||||||
}
|
}
|
||||||
|
#elseif os(tvOS)
|
||||||
|
if let data = UserDefaults.standard.data(forKey: "segmentsColorData"),
|
||||||
|
let uiColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) {
|
||||||
|
return Color(uiColor)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return .yellow
|
return .yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,7 +611,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
func holdForPause() {
|
func holdForPause() {
|
||||||
let holdForPauseGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldForPause(_:)))
|
let holdForPauseGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldForPause(_:)))
|
||||||
holdForPauseGesture.minimumPressDuration = 1
|
holdForPauseGesture.minimumPressDuration = 1
|
||||||
|
#if !os(tvOS)
|
||||||
holdForPauseGesture.numberOfTouchesRequired = 2
|
holdForPauseGesture.numberOfTouchesRequired = 2
|
||||||
|
#endif
|
||||||
view.addGestureRecognizer(holdForPauseGesture)
|
view.addGestureRecognizer(holdForPauseGesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -832,9 +849,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
|
|
||||||
func volumeSlider() {
|
func volumeSlider() {
|
||||||
let container = VolumeSliderContainer(volumeVM: self.volumeViewModel) { newVal in
|
let container = VolumeSliderContainer(volumeVM: self.volumeViewModel) { newVal in
|
||||||
|
#if !os(tvOS)
|
||||||
if let sysSlider = self.systemVolumeSlider {
|
if let sysSlider = self.systemVolumeSlider {
|
||||||
sysSlider.value = Float(newVal)
|
sysSlider.value = Float(newVal)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
let hostingController = UIHostingController(rootView: container)
|
let hostingController = UIHostingController(rootView: container)
|
||||||
|
|
@ -1042,7 +1061,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
skipIntroButton.setImage(introImage, for: .normal)
|
skipIntroButton.setImage(introImage, for: .normal)
|
||||||
|
|
||||||
skipIntroButton.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
skipIntroButton.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||||
|
#if !os(tvOS)
|
||||||
skipIntroButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
|
skipIntroButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
|
||||||
|
#endif
|
||||||
skipIntroButton.tintColor = .white
|
skipIntroButton.tintColor = .white
|
||||||
skipIntroButton.setTitleColor(.white, for: .normal)
|
skipIntroButton.setTitleColor(.white, for: .normal)
|
||||||
skipIntroButton.layer.cornerRadius = 21
|
skipIntroButton.layer.cornerRadius = 21
|
||||||
|
|
@ -1074,7 +1095,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
skipOutroButton.setImage(outroImage, for: .normal)
|
skipOutroButton.setImage(outroImage, for: .normal)
|
||||||
|
|
||||||
skipOutroButton.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
skipOutroButton.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||||
|
#if !os(tvOS)
|
||||||
skipOutroButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
|
skipOutroButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
|
||||||
|
#endif
|
||||||
skipOutroButton.tintColor = .white
|
skipOutroButton.tintColor = .white
|
||||||
skipOutroButton.setTitleColor(.white, for: .normal)
|
skipOutroButton.setTitleColor(.white, for: .normal)
|
||||||
skipOutroButton.layer.cornerRadius = 21
|
skipOutroButton.layer.cornerRadius = 21
|
||||||
|
|
@ -1248,7 +1271,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
skip85Button.setImage(image, for: .normal)
|
skip85Button.setImage(image, for: .normal)
|
||||||
|
|
||||||
skip85Button.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
skip85Button.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||||
|
#if !os(tvOS)
|
||||||
skip85Button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
|
skip85Button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
|
||||||
|
#endif
|
||||||
skip85Button.tintColor = .white
|
skip85Button.tintColor = .white
|
||||||
skip85Button.setTitleColor(.white, for: .normal)
|
skip85Button.setTitleColor(.white, for: .normal)
|
||||||
skip85Button.layer.cornerRadius = 21
|
skip85Button.layer.cornerRadius = 21
|
||||||
|
|
@ -2092,7 +2117,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
self.subtitleBackgroundEnabled = settings.backgroundEnabled
|
self.subtitleBackgroundEnabled = settings.backgroundEnabled
|
||||||
self.subtitleBottomPadding = settings.bottomPadding
|
self.subtitleBottomPadding = settings.bottomPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||||
if UserDefaults.standard.bool(forKey: "alwaysLandscape") {
|
if UserDefaults.standard.bool(forKey: "alwaysLandscape") {
|
||||||
return .landscape
|
return .landscape
|
||||||
|
|
@ -2108,13 +2134,14 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func setupAudioSession() {
|
func setupAudioSession() {
|
||||||
do {
|
do {
|
||||||
let audioSession = AVAudioSession.sharedInstance()
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
try audioSession.setCategory(.playback, mode: .moviePlayback, options: .mixWithOthers)
|
try audioSession.setCategory(.playback, mode: .moviePlayback, options: .mixWithOthers)
|
||||||
try audioSession.setActive(true)
|
try audioSession.setActive(true)
|
||||||
try audioSession.overrideOutputAudioPort(.speaker)
|
// try audioSession.overrideOutputAudioPort(.speaker)
|
||||||
} catch {
|
} catch {
|
||||||
Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: "Debug")
|
Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: "Debug")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ class NormalPlayer: AVPlayerViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
setupHoldGesture()
|
setupHoldGesture()
|
||||||
setupAudioSession()
|
#if !os(tvOS)
|
||||||
|
setupAudioSession()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupHoldGesture() {
|
private func setupHoldGesture() {
|
||||||
|
|
@ -52,8 +54,9 @@ class NormalPlayer: AVPlayerViewController {
|
||||||
let audioSession = AVAudioSession.sharedInstance()
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
try audioSession.setCategory(.playback, mode: .moviePlayback, options: .mixWithOthers)
|
try audioSession.setCategory(.playback, mode: .moviePlayback, options: .mixWithOthers)
|
||||||
try audioSession.setActive(true)
|
try audioSession.setActive(true)
|
||||||
|
#if !os(tvOS)
|
||||||
try audioSession.overrideOutputAudioPort(.speaker)
|
try audioSession.overrideOutputAudioPort(.speaker)
|
||||||
|
#endif
|
||||||
} catch {
|
} catch {
|
||||||
Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: "Debug")
|
Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: "Debug")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ class VideoPlayerViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||||
if UserDefaults.standard.bool(forKey: "alwaysLandscape") {
|
if UserDefaults.standard.bool(forKey: "alwaysLandscape") {
|
||||||
return .landscape
|
return .landscape
|
||||||
|
|
@ -163,7 +163,7 @@ class VideoPlayerViewController: UIViewController {
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
deinit {
|
deinit {
|
||||||
player?.pause()
|
player?.pause()
|
||||||
if let timeObserverToken = timeObserverToken {
|
if let timeObserverToken = timeObserverToken {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
#if !os(tvOS)
|
||||||
import WebKit
|
import WebKit
|
||||||
|
#endif
|
||||||
|
|
||||||
private struct ModuleLink: Identifiable {
|
private struct ModuleLink: Identifiable {
|
||||||
let id = UUID()
|
let id = UUID()
|
||||||
|
|
@ -28,7 +30,7 @@ struct CommunityLibraryView: View {
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
WebView(url: webURL) { linkURL in
|
WebView(url: webURL) { linkURL in
|
||||||
|
|
||||||
if let comps = URLComponents(url: linkURL, resolvingAgainstBaseURL: false),
|
if let comps = URLComponents(url: linkURL, resolvingAgainstBaseURL: false),
|
||||||
|
|
@ -37,6 +39,7 @@ struct CommunityLibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ignoresSafeArea(edges: .top)
|
.ignoresSafeArea(edges: .top)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.onAppear(perform: loadURL)
|
.onAppear(perform: loadURL)
|
||||||
.sheet(item: $moduleLinkToAdd) { link in
|
.sheet(item: $moduleLinkToAdd) { link in
|
||||||
|
|
@ -61,6 +64,7 @@ struct CommunityLibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
struct WebView: UIViewRepresentable {
|
struct WebView: UIViewRepresentable {
|
||||||
let url: URL?
|
let url: URL?
|
||||||
let onCustomScheme: (URL) -> Void
|
let onCustomScheme: (URL) -> Void
|
||||||
|
|
@ -102,3 +106,4 @@ struct WebView: UIViewRepresentable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,19 @@ struct ModuleAdditionSettingsView: View {
|
||||||
InfoRow(title: "Quality", value: metadata.quality)
|
InfoRow(title: "Quality", value: metadata.quality)
|
||||||
InfoRow(title: "Stream Typed", value: metadata.streamType)
|
InfoRow(title: "Stream Typed", value: metadata.streamType)
|
||||||
InfoRow(title: "Base URL", value: metadata.baseUrl)
|
InfoRow(title: "Base URL", value: metadata.baseUrl)
|
||||||
|
#if !os(tvOS)
|
||||||
.onLongPressGesture {
|
.onLongPressGesture {
|
||||||
UIPasteboard.general.string = metadata.baseUrl
|
UIPasteboard.general.string = metadata.baseUrl
|
||||||
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
InfoRow(title: "Script URL", value: metadata.scriptUrl)
|
InfoRow(title: "Script URL", value: metadata.scriptUrl)
|
||||||
|
#if !os(tvOS)
|
||||||
.onLongPressGesture {
|
.onLongPressGesture {
|
||||||
UIPasteboard.general.string = metadata.scriptUrl
|
UIPasteboard.general.string = metadata.scriptUrl
|
||||||
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ModuleManager: ObservableObject {
|
class ModuleManager: ObservableObject, @unchecked Sendable {
|
||||||
@Published var modules: [ScrapingModule] = []
|
@Published var modules: [ScrapingModule] = []
|
||||||
|
|
||||||
private let fileManager = FileManager.default
|
private let fileManager = FileManager.default
|
||||||
|
|
@ -59,7 +59,12 @@ class ModuleManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getDocumentsDirectory() -> URL {
|
private func getDocumentsDirectory() -> URL {
|
||||||
fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
#if !os(tvOS)
|
||||||
|
let directory = FileManager.SearchPathDirectory.documentDirectory
|
||||||
|
#elseif os(tvOS)
|
||||||
|
let directory = FileManager.SearchPathDirectory.cachesDirectory
|
||||||
|
#endif
|
||||||
|
return fileManager.urls(for: directory, in: .userDomainMask)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getModulesFilePath() -> URL {
|
private func getModulesFilePath() -> URL {
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ struct LibraryView: View {
|
||||||
@State private var isDetailActive: Bool = false
|
@State private var isDetailActive: Bool = false
|
||||||
|
|
||||||
@State private var continueWatchingItems: [ContinueWatchingItem] = []
|
@State private var continueWatchingItems: [ContinueWatchingItem] = []
|
||||||
|
#if !os(tvOS)
|
||||||
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
||||||
|
#endif
|
||||||
private let columns = [
|
private let columns = [
|
||||||
GridItem(.adaptive(minimum: 150), spacing: 12)
|
GridItem(.adaptive(minimum: 150), spacing: 12)
|
||||||
]
|
]
|
||||||
|
|
@ -166,9 +167,11 @@ struct LibraryView: View {
|
||||||
.onAppear {
|
.onAppear {
|
||||||
updateOrientation()
|
updateOrientation()
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||||
updateOrientation()
|
updateOrientation()
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, 20)
|
.padding(.vertical, 20)
|
||||||
|
|
@ -201,13 +204,19 @@ struct LibraryView: View {
|
||||||
|
|
||||||
private func updateOrientation() {
|
private func updateOrientation() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
#if !os(tvOS)
|
||||||
isLandscape = UIDevice.current.orientation.isLandscape
|
isLandscape = UIDevice.current.orientation.isLandscape
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func determineColumns() -> Int {
|
private func determineColumns() -> Int {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
|
#if !os(tvOS)
|
||||||
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||||
|
#elseif os(tvOS)
|
||||||
|
return mediaColumnsLandscape
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ struct EpisodeCell: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
#if !os(tvOS)
|
||||||
HStack {
|
HStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
KFImage(URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl))
|
KFImage(URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl))
|
||||||
|
|
@ -58,13 +59,7 @@ struct EpisodeCell: View {
|
||||||
.aspectRatio(16/9, contentMode: .fill)
|
.aspectRatio(16/9, contentMode: .fill)
|
||||||
.frame(width: 100, height: 56)
|
.frame(width: 100, height: 56)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
|
|
||||||
if isLoading {
|
|
||||||
ProgressView()
|
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Episode \(episodeID + 1)")
|
Text("Episode \(episodeID + 1)")
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
|
|
@ -73,44 +68,102 @@ struct EpisodeCell: View {
|
||||||
.font(.system(size: 13))
|
.font(.system(size: 13))
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
Spacer()
|
||||||
|
CircularProgressBar(progress: currentProgress)
|
||||||
Spacer()
|
.frame(width: 40, height: 40)
|
||||||
|
}.contentShape(Rectangle())
|
||||||
CircularProgressBar(progress: currentProgress)
|
.contextMenu {
|
||||||
.frame(width: 40, height: 40)
|
if progress <= 0.9 {
|
||||||
}
|
Button(action: markAsWatched) {
|
||||||
.contentShape(Rectangle())
|
Label("Mark as Watched", systemImage: "checkmark.circle")
|
||||||
.contextMenu {
|
}
|
||||||
if progress <= 0.9 {
|
if progress != 0 {
|
||||||
Button(action: markAsWatched) {
|
Button(action: resetProgress) {
|
||||||
Label("Mark as Watched", systemImage: "checkmark.circle")
|
Label("Reset Progress", systemImage: "arrow.counterclockwise")
|
||||||
|
if episodeIndex > 0 {
|
||||||
|
Button(action: onMarkAllPrevious) {
|
||||||
|
Label("Mark All Previous Watched", systemImage: "checkmark.circle.fill")
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
updateProgress()
|
||||||
|
fetchEpisodeDetails()
|
||||||
|
}
|
||||||
|
.onChange(of: progress) { _ in
|
||||||
|
updateProgress()
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl
|
||||||
|
onTap(imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if progress != 0 {
|
|
||||||
Button(action: resetProgress) {
|
|
||||||
Label("Reset Progress", systemImage: "arrow.counterclockwise")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if episodeIndex > 0 {
|
|
||||||
Button(action: onMarkAllPrevious) {
|
|
||||||
Label("Mark All Previous Watched", systemImage: "checkmark.circle.fill")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onAppear {
|
#elseif os(tvOS)
|
||||||
updateProgress()
|
Button{
|
||||||
fetchEpisodeDetails()
|
|
||||||
}
|
|
||||||
.onChange(of: progress) { _ in
|
|
||||||
updateProgress()
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl
|
let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl
|
||||||
onTap(imageUrl)
|
onTap(imageUrl)
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
ZStack {
|
||||||
|
KFImage(URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl))
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(16/9, contentMode: .fill)
|
||||||
|
.frame(width: 100, height: 56)
|
||||||
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(CircularProgressViewStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Episode \(episodeID + 1)")
|
||||||
|
.font(.system(size: 15))
|
||||||
|
if !episodeTitle.isEmpty {
|
||||||
|
Text(episodeTitle)
|
||||||
|
.font(.system(size: 13))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
CircularProgressBar(progress: currentProgress)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.contextMenu {
|
||||||
|
if progress <= 0.9 {
|
||||||
|
Button(action: markAsWatched) {
|
||||||
|
Label("Mark as Watched", systemImage: "checkmark.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if progress != 0 {
|
||||||
|
Button(action: resetProgress) {
|
||||||
|
Label("Reset Progress", systemImage: "arrow.counterclockwise")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if episodeIndex > 0 {
|
||||||
|
Button(action: onMarkAllPrevious) {
|
||||||
|
Label("Mark All Previous Watched", systemImage: "checkmark.circle.fill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
updateProgress()
|
||||||
|
fetchEpisodeDetails()
|
||||||
|
}
|
||||||
|
.onChange(of: progress) { oldValue, _ in
|
||||||
|
updateProgress()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func markAsWatched() {
|
private func markAsWatched() {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
|
#if !os(tvOS)
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
#endif
|
||||||
|
|
||||||
struct MediaItem: Identifiable {
|
struct MediaItem: Identifiable {
|
||||||
let id = UUID()
|
let id = UUID()
|
||||||
|
|
@ -92,8 +94,10 @@ struct MediaInfoView: View {
|
||||||
.font(.system(size: 17))
|
.font(.system(size: 17))
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.onLongPressGesture {
|
.onLongPressGesture {
|
||||||
|
#if !os(tvOS)
|
||||||
UIPasteboard.general.string = title
|
UIPasteboard.general.string = title
|
||||||
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if !aliases.isEmpty && aliases != title && aliases != "N/A" && aliases != "No Data" {
|
if !aliases.isEmpty && aliases != title && aliases != "N/A" && aliases != "No Data" {
|
||||||
|
|
@ -122,7 +126,9 @@ struct MediaInfoView: View {
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 12) {
|
HStack(alignment: .center, spacing: 12) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
#if !os(tvOS)
|
||||||
openSafariViewController(with: href)
|
openSafariViewController(with: href)
|
||||||
|
#endif
|
||||||
}) {
|
}) {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Text(module.metadata.sourceName)
|
Text(module.metadata.sourceName)
|
||||||
|
|
@ -164,9 +170,11 @@ struct MediaInfoView: View {
|
||||||
|
|
||||||
if let id = itemID ?? customAniListID {
|
if let id = itemID ?? customAniListID {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
#if !os(tvOS)
|
||||||
if let url = URL(string: "https://anilist.co/anime/\(id)") {
|
if let url = URL(string: "https://anilist.co/anime/\(id)") {
|
||||||
openSafariViewController(with: url.absoluteString)
|
openSafariViewController(with: url.absoluteString)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}) {
|
}) {
|
||||||
Label("Open in AniList", systemImage: "link")
|
Label("Open in AniList", systemImage: "link")
|
||||||
}
|
}
|
||||||
|
|
@ -418,7 +426,9 @@ struct MediaInfoView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
#if !os(tvOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
#endif
|
||||||
.navigationBarTitle("")
|
.navigationBarTitle("")
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
}
|
}
|
||||||
|
|
@ -452,10 +462,11 @@ struct MediaInfoView: View {
|
||||||
}
|
}
|
||||||
selectedRange = 0..<episodeChunkSize
|
selectedRange = 0..<episodeChunkSize
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||||
orientationChanged.toggle()
|
orientationChanged.toggle()
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if showStreamLoadingView {
|
if showStreamLoadingView {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Text("Loading \(currentStreamTitle)…")
|
Text("Loading \(currentStreamTitle)…")
|
||||||
|
|
@ -774,8 +785,9 @@ struct MediaInfoView: View {
|
||||||
AnalyticsManager.shared.sendEvent(event: "error", additionalData: ["error": error, "message": "Failed to fetch stream"])
|
AnalyticsManager.shared.sendEvent(event: "error", additionalData: ["error": error, "message": "Failed to fetch stream"])
|
||||||
}
|
}
|
||||||
DropManager.shared.showDrop(title: "Stream not Found", subtitle: "", duration: 0.5, icon: UIImage(systemName: "xmark"))
|
DropManager.shared.showDrop(title: "Stream not Found", subtitle: "", duration: 0.5, icon: UIImage(systemName: "xmark"))
|
||||||
|
#if !os(tvOS)
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
#endif
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -847,7 +859,7 @@ struct MediaInfoView: View {
|
||||||
self.isFetchingEpisode = false
|
self.isFetchingEpisode = false
|
||||||
self.showStreamLoadingView = false
|
self.showStreamLoadingView = false
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let externalPlayer = UserDefaults.standard.string(forKey: "externalPlayer") ?? "Sora"
|
let externalPlayer = UserDefaults.standard.string(forKey: "externalPlayer") ?? "Default"
|
||||||
var scheme: String?
|
var scheme: String?
|
||||||
|
|
||||||
switch externalPlayer {
|
switch externalPlayer {
|
||||||
|
|
@ -929,6 +941,7 @@ struct MediaInfoView: View {
|
||||||
DropManager.shared.showDrop(title: "Fetching Next Episode", subtitle: "", duration: 0.5, icon: UIImage(systemName: "arrow.triangle.2.circlepath"))
|
DropManager.shared.showDrop(title: "Fetching Next Episode", subtitle: "", duration: 0.5, icon: UIImage(systemName: "arrow.triangle.2.circlepath"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
private func openSafariViewController(with urlString: String) {
|
private func openSafariViewController(with urlString: String) {
|
||||||
guard let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) else {
|
guard let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) else {
|
||||||
Logger.shared.log("Unable to open the webpage", type: "Error")
|
Logger.shared.log("Unable to open the webpage", type: "Error")
|
||||||
|
|
@ -940,6 +953,7 @@ struct MediaInfoView: View {
|
||||||
rootVC.present(safariViewController, animated: true, completion: nil)
|
rootVC.present(safariViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func cleanTitle(_ title: String?) -> String {
|
private func cleanTitle(_ title: String?) -> String {
|
||||||
guard let title = title else { return "Unknown" }
|
guard let title = title else { return "Unknown" }
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ struct SearchView: View {
|
||||||
@State private var isSearching = false
|
@State private var isSearching = false
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@State private var hasNoResults = false
|
@State private var hasNoResults = false
|
||||||
|
#if !os(tvOS)
|
||||||
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
||||||
|
#endif
|
||||||
@State private var isModuleSelectorPresented = false
|
@State private var isModuleSelectorPresented = false
|
||||||
|
|
||||||
private var selectedModule: ScrapingModule? {
|
private var selectedModule: ScrapingModule? {
|
||||||
|
|
@ -47,12 +49,16 @@ struct SearchView: View {
|
||||||
]
|
]
|
||||||
|
|
||||||
private var columnsCount: Int {
|
private var columnsCount: Int {
|
||||||
|
#if !os(tvOS)
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
let isLandscape = UIScreen.main.bounds.width > UIScreen.main.bounds.height
|
let isLandscape = UIScreen.main.bounds.width > UIScreen.main.bounds.height
|
||||||
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||||
} else {
|
} else {
|
||||||
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||||
}
|
}
|
||||||
|
#elseif os(tvOS)
|
||||||
|
return mediaColumnsLandscape
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cellWidth: CGFloat {
|
private var cellWidth: CGFloat {
|
||||||
|
|
@ -61,11 +67,16 @@ struct SearchView: View {
|
||||||
.first
|
.first
|
||||||
let safeAreaInsets = keyWindow?.safeAreaInsets ?? .zero
|
let safeAreaInsets = keyWindow?.safeAreaInsets ?? .zero
|
||||||
let safeWidth = UIScreen.main.bounds.width - safeAreaInsets.left - safeAreaInsets.right
|
let safeWidth = UIScreen.main.bounds.width - safeAreaInsets.left - safeAreaInsets.right
|
||||||
|
#if !os(tvOS)
|
||||||
let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1)
|
let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1)
|
||||||
|
#elseif os(tvOS)
|
||||||
|
let totalSpacing: CGFloat = 32 * CGFloat(columnsCount + 1)
|
||||||
|
#endif
|
||||||
let availableWidth = safeWidth - totalSpacing
|
let availableWidth = safeWidth - totalSpacing
|
||||||
return availableWidth / CGFloat(columnsCount)
|
return availableWidth / CGFloat(columnsCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|
@ -222,6 +233,159 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elseif os(tvOS)
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
ScrollView {
|
||||||
|
let columnsCount = determineColumns()
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack {
|
||||||
|
SearchBar(text: $searchText, onSearchButtonClicked: performSearch)
|
||||||
|
.padding(.leading)
|
||||||
|
.padding(.trailing, searchText.isEmpty ? 16 : 0)
|
||||||
|
.disabled(selectedModule == nil)
|
||||||
|
.padding(.top)
|
||||||
|
|
||||||
|
if !searchText.isEmpty {
|
||||||
|
Button("Cancel") {
|
||||||
|
searchText = ""
|
||||||
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||||
|
}
|
||||||
|
.padding(.trailing)
|
||||||
|
.padding(.top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedModule == nil {
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Image(systemName: "questionmark.app")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Text("No Module Selected")
|
||||||
|
.font(.headline)
|
||||||
|
Text("Please select a module from settings")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.shadow(color: Color.black.opacity(0.1), radius: 2, y: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !searchText.isEmpty {
|
||||||
|
if isSearching {
|
||||||
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||||
|
ForEach(0..<columnsCount*4, id: \.self) { _ in
|
||||||
|
SearchSkeletonCell(cellWidth: cellWidth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
.padding()
|
||||||
|
} else if hasNoResults {
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Image(systemName: "magnifyingglass")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Text("No Results Found")
|
||||||
|
.font(.headline)
|
||||||
|
Text("Try different keywords")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.top)
|
||||||
|
} else {
|
||||||
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||||
|
ForEach(searchItems) { item in
|
||||||
|
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)) {
|
||||||
|
VStack {
|
||||||
|
KFImage(URL(string: item.imageUrl))
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(height: cellWidth * 3 / 2)
|
||||||
|
.frame(maxWidth: cellWidth - 60)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.clipped()
|
||||||
|
Text(item.title)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(Color.primary)
|
||||||
|
.padding([.leading, .bottom], 8)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Search")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
ForEach(getModuleLanguageGroups(), id: \.self) { language in
|
||||||
|
Menu {
|
||||||
|
ForEach(getModulesForLanguage(language), id: \.id) { module in
|
||||||
|
Button {
|
||||||
|
selectedModuleId = module.id.uuidString
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
KFImage(URL(string: module.metadata.iconUrl))
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.cornerRadius(4)
|
||||||
|
Text(module.metadata.sourceName)
|
||||||
|
if module.id.uuidString == selectedModuleId {
|
||||||
|
Image(systemName: "checkmark")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
Text(language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
if let selectedModule = selectedModule {
|
||||||
|
Text(selectedModule.metadata.sourceName)
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} else {
|
||||||
|
Text("Select Module")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
Image(systemName: "chevron.down")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
.onChange(of: selectedModuleId) { oldValue, _ in
|
||||||
|
if !searchText.isEmpty {
|
||||||
|
performSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchText) { oldValue, newValue in
|
||||||
|
if newValue.isEmpty {
|
||||||
|
searchItems = []
|
||||||
|
hasNoResults = false
|
||||||
|
isSearching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func performSearch() {
|
private func performSearch() {
|
||||||
Logger.shared.log("Searching for: \(searchText)", type: "General")
|
Logger.shared.log("Searching for: \(searchText)", type: "General")
|
||||||
guard !searchText.isEmpty, let module = selectedModule else {
|
guard !searchText.isEmpty, let module = selectedModule else {
|
||||||
|
|
@ -260,19 +424,25 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
private func updateOrientation() {
|
private func updateOrientation() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
isLandscape = UIDevice.current.orientation.isLandscape
|
isLandscape = UIDevice.current.orientation.isLandscape
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func determineColumns() -> Int {
|
private func determineColumns() -> Int {
|
||||||
|
#if !os(tvOS)
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||||
} else {
|
} else {
|
||||||
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||||
}
|
}
|
||||||
|
#elseif os(tvOS)
|
||||||
|
return mediaColumnsLandscape
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cleanLanguageName(_ language: String?) -> String {
|
private func cleanLanguageName(_ language: String?) -> String {
|
||||||
|
|
@ -321,9 +491,11 @@ struct SearchBar: View {
|
||||||
TextField("Search...", text: $text, onCommit: onSearchButtonClicked)
|
TextField("Search...", text: $text, onCommit: onSearchButtonClicked)
|
||||||
.padding(7)
|
.padding(7)
|
||||||
.padding(.horizontal, 25)
|
.padding(.horizontal, 25)
|
||||||
|
#if !os(tvOS)
|
||||||
.background(Color(.systemGray6))
|
.background(Color(.systemGray6))
|
||||||
|
#endif
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
.onChange(of: text){newValue in
|
.onChange(of: text){ newValue in
|
||||||
debounceTimer?.invalidate()
|
debounceTimer?.invalidate()
|
||||||
// Start a new timer to wait before performing the action
|
// Start a new timer to wait before performing the action
|
||||||
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,12 @@ struct SettingsViewData: View {
|
||||||
|
|
||||||
func removeAllFilesInDocuments() {
|
func removeAllFilesInDocuments() {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
|
#if !os(tvOS)
|
||||||
|
let directory = FileManager.SearchPathDirectory.documentDirectory
|
||||||
|
#elseif os(tvOS)
|
||||||
|
let directory = FileManager.SearchPathDirectory.cachesDirectory
|
||||||
|
#endif
|
||||||
|
if let documentsURL = fileManager.urls(for: directory, in: .userDomainMask).first {
|
||||||
do {
|
do {
|
||||||
let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil)
|
let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil)
|
||||||
for fileURL in fileURLs {
|
for fileURL in fileURLs {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ struct SettingsViewGeneral: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section(header: Text("Interface")) {
|
Section(header: Text("Interface")) {
|
||||||
|
#if !os(tvOS)
|
||||||
ColorPicker("Accent Color", selection: $settings.accentColor)
|
ColorPicker("Accent Color", selection: $settings.accentColor)
|
||||||
|
#endif
|
||||||
HStack {
|
HStack {
|
||||||
Text("Appearance")
|
Text("Appearance")
|
||||||
Picker("Appearance", selection: $settings.selectedAppearance) {
|
Picker("Appearance", selection: $settings.selectedAppearance) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ struct SettingsViewLogger: View {
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding()
|
.padding()
|
||||||
|
#if !os(tvOS)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.navigationTitle("Logs")
|
.navigationTitle("Logs")
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
@ -30,12 +32,14 @@ struct SettingsViewLogger: View {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack {
|
HStack {
|
||||||
Menu {
|
Menu {
|
||||||
|
#if !os(tvOS)
|
||||||
Button(action: {
|
Button(action: {
|
||||||
UIPasteboard.general.string = logs
|
UIPasteboard.general.string = logs
|
||||||
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
||||||
}) {
|
}) {
|
||||||
Label("Copy to Clipboard", systemImage: "doc.on.doc")
|
Label("Copy to Clipboard", systemImage: "doc.on.doc")
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
Button(role: .destructive, action: {
|
Button(role: .destructive, action: {
|
||||||
Logger.shared.clearLogs()
|
Logger.shared.clearLogs()
|
||||||
logs = Logger.shared.getLogs()
|
logs = Logger.shared.getLogs()
|
||||||
|
|
|
||||||
|
|
@ -88,12 +88,14 @@ struct SettingsViewModule: View {
|
||||||
selectedModuleId = module.id.uuidString
|
selectedModuleId = module.id.uuidString
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
|
#if !os(tvOS)
|
||||||
Button(action: {
|
Button(action: {
|
||||||
UIPasteboard.general.string = module.metadataUrl
|
UIPasteboard.general.string = module.metadataUrl
|
||||||
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill"))
|
||||||
}) {
|
}) {
|
||||||
Label("Copy URL", systemImage: "doc.on.doc")
|
Label("Copy URL", systemImage: "doc.on.doc")
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
if selectedModuleId != module.id.uuidString {
|
if selectedModuleId != module.id.uuidString {
|
||||||
moduleManager.deleteModule(module)
|
moduleManager.deleteModule(module)
|
||||||
|
|
@ -104,6 +106,7 @@ struct SettingsViewModule: View {
|
||||||
}
|
}
|
||||||
.disabled(selectedModuleId == module.id.uuidString)
|
.disabled(selectedModuleId == module.id.uuidString)
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
.swipeActions {
|
.swipeActions {
|
||||||
if selectedModuleId != module.id.uuidString {
|
if selectedModuleId != module.id.uuidString {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
|
|
@ -114,6 +117,7 @@ struct SettingsViewModule: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +184,11 @@ struct SettingsViewModule: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
func showAddModuleAlert() {
|
func showAddModuleAlert() {
|
||||||
|
#if !os(tvOS)
|
||||||
let pasteboardString = UIPasteboard.general.string ?? ""
|
let pasteboardString = UIPasteboard.general.string ?? ""
|
||||||
|
#elseif os(tvOS)
|
||||||
|
let pasteboardString = ""
|
||||||
|
#endif
|
||||||
|
|
||||||
if !pasteboardString.isEmpty {
|
if !pasteboardString.isEmpty {
|
||||||
let clipboardAlert = UIAlertController(
|
let clipboardAlert = UIAlertController(
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ struct SettingsViewPlayer: View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Hold Speed:")
|
Text("Hold Speed:")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
#if !os(tvOS)
|
||||||
Stepper(
|
Stepper(
|
||||||
value: $holdSpeedPlayer,
|
value: $holdSpeedPlayer,
|
||||||
in: 0.25...2.5,
|
in: 0.25...2.5,
|
||||||
|
|
@ -59,9 +60,10 @@ struct SettingsViewPlayer: View {
|
||||||
) {
|
) {
|
||||||
Text(String(format: "%.2f", holdSpeedPlayer))
|
Text(String(format: "%.2f", holdSpeedPlayer))
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
Section(header: Text("Progress bar Marker Color")) {
|
Section(header: Text("Progress bar Marker Color")) {
|
||||||
ColorPicker("Segments Color", selection: Binding(
|
ColorPicker("Segments Color", selection: Binding(
|
||||||
get: {
|
get: {
|
||||||
|
|
@ -82,18 +84,23 @@ struct SettingsViewPlayer: View {
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Section(header: Text("Skip Settings"), footer : Text("Double tapping the screen on it's sides will skip with the short tap setting.")) {
|
Section(header: Text("Skip Settings"), footer : Text("Double tapping the screen on it's sides will skip with the short tap setting.")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Tap Skip:")
|
Text("Tap Skip:")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
#if !os(tvOS)
|
||||||
Stepper("\(Int(skipIncrement))s", value: $skipIncrement, in: 5...300, step: 5)
|
Stepper("\(Int(skipIncrement))s", value: $skipIncrement, in: 5...300, step: 5)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Long press Skip:")
|
Text("Long press Skip:")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
#if !os(tvOS)
|
||||||
Stepper("\(Int(skipIncrementHold))s", value: $skipIncrementHold, in: 5...300, step: 5)
|
Stepper("\(Int(skipIncrementHold))s", value: $skipIncrementHold, in: 5...300, step: 5)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle("Double Tap to Seek", isOn: $doubleTapSeekEnabled)
|
Toggle("Double Tap to Seek", isOn: $doubleTapSeekEnabled)
|
||||||
|
|
@ -159,32 +166,38 @@ struct SubtitleSettingsSection: View {
|
||||||
|
|
||||||
Toggle("Background Enabled", isOn: $backgroundEnabled)
|
Toggle("Background Enabled", isOn: $backgroundEnabled)
|
||||||
.tint(.accentColor)
|
.tint(.accentColor)
|
||||||
|
#if !os(tvOS)
|
||||||
.onChange(of: backgroundEnabled) { newValue in
|
.onChange(of: backgroundEnabled) { newValue in
|
||||||
SubtitleSettingsManager.shared.update { settings in
|
SubtitleSettingsManager.shared.update { settings in
|
||||||
settings.backgroundEnabled = newValue
|
settings.backgroundEnabled = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Font Size:")
|
Text("Font Size:")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
#if !os(tvOS)
|
||||||
Stepper("\(Int(fontSize))", value: $fontSize, in: 12...36, step: 1)
|
Stepper("\(Int(fontSize))", value: $fontSize, in: 12...36, step: 1)
|
||||||
.onChange(of: fontSize) { newValue in
|
.onChange(of: fontSize) { newValue in
|
||||||
SubtitleSettingsManager.shared.update { settings in
|
SubtitleSettingsManager.shared.update { settings in
|
||||||
settings.fontSize = newValue
|
settings.fontSize = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Bottom Padding:")
|
Text("Bottom Padding:")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
#if !os(tvOS)
|
||||||
Stepper("\(Int(bottomPadding))", value: $bottomPadding, in: 0...50, step: 1)
|
Stepper("\(Int(bottomPadding))", value: $bottomPadding, in: 0...50, step: 1)
|
||||||
.onChange(of: bottomPadding) { newValue in
|
.onChange(of: bottomPadding) { newValue in
|
||||||
SubtitleSettingsManager.shared.update { settings in
|
SubtitleSettingsManager.shared.update { settings in
|
||||||
settings.bottomPadding = newValue
|
settings.bottomPadding = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -474,7 +474,7 @@
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1320;
|
LastSwiftUpdateCheck = 1320;
|
||||||
LastUpgradeCheck = 1320;
|
LastUpgradeCheck = 1630;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
133D7C692D2BE2500075467E = {
|
133D7C692D2BE2500075467E = {
|
||||||
CreatedOnToolsVersion = 13.2.1;
|
CreatedOnToolsVersion = 13.2.1;
|
||||||
|
|
@ -487,6 +487,7 @@
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
en,
|
en,
|
||||||
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 133D7C612D2BE2500075467E;
|
mainGroup = 133D7C612D2BE2500075467E;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
|
@ -615,8 +616,10 @@
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = 399LMK6Q2Y;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
|
@ -677,8 +680,10 @@
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = 399LMK6Q2Y;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
|
@ -709,7 +714,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 399LMK6Q2Y;
|
DEVELOPMENT_TEAM = "";
|
||||||
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
|
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
|
@ -734,10 +739,11 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||||
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -752,7 +758,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 399LMK6Q2Y;
|
DEVELOPMENT_TEAM = "";
|
||||||
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
|
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
|
@ -777,10 +783,11 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||||
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue