merge dev into features, add new translations, bundle tracker icons instead of loading them by url, group assets by type, make uicolor codable -> ui alignment: "subtitle color" setting now using the ColorPicker, fix weird tracker dividers, change tabview order, cleanup
|
|
@ -34,6 +34,7 @@
|
|||
}
|
||||
},
|
||||
"%@ - Episode %lld" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -864,8 +865,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Download Stream" : {
|
||||
|
||||
},
|
||||
"Downloads" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -928,6 +933,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enter HLS URL" : {
|
||||
|
||||
},
|
||||
"Episode %lld" : {
|
||||
"localizations" : {
|
||||
|
|
@ -1249,6 +1257,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"HLS Downloader" : {
|
||||
|
||||
},
|
||||
"Hold Speed:" : {
|
||||
"localizations" : {
|
||||
|
|
@ -1315,7 +1326,16 @@
|
|||
}
|
||||
},
|
||||
"Key" : {
|
||||
"extractionState" : "manual"
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"Landscape Columns" : {
|
||||
"localizations" : {
|
||||
|
|
@ -1956,6 +1976,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"No offline content available" : {
|
||||
|
||||
},
|
||||
"No Results Found" : {
|
||||
"localizations" : {
|
||||
|
|
@ -2036,6 +2059,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Play Offline Content" : {
|
||||
|
||||
},
|
||||
"Player" : {
|
||||
"localizations" : {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 248 KiB |
0
Sora/Assets.xcassets/AppIcon_Pixel.appiconset/darkmode.png → Sora/Assets.xcassets/AppIcons/AppIcon_Pixel.appiconset/darkmode.png
Executable file → Normal file
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
0
Sora/Assets.xcassets/AppIcon_Pixel.appiconset/tinting.png → Sora/Assets.xcassets/AppIcons/AppIcon_Pixel.appiconset/tinting.png
Executable file → Normal file
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
0
Sora/Assets.xcassets/AppIcon_Pride.appiconset/darkmode.png → Sora/Assets.xcassets/AppIcons/AppIcon_Pride.appiconset/darkmode.png
Executable file → Normal file
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 179 KiB |
0
Sora/Assets.xcassets/AppIcon_Pride.appiconset/tinting.png → Sora/Assets.xcassets/AppIcons/AppIcon_Pride.appiconset/tinting.png
Executable file → Normal file
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 169 KiB |
6
Sora/Assets.xcassets/AppIcons/Contents.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Sora/Assets.xcassets/TrackerIcons/AniList.imageset/AniList.png
vendored
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
15
Sora/Assets.xcassets/TrackerIcons/AniList.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AniList.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
||||
6
Sora/Assets.xcassets/TrackerIcons/Contents.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
15
Sora/Assets.xcassets/TrackerIcons/Trakt.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Trakt.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
||||
BIN
Sora/Assets.xcassets/TrackerIcons/Trakt.imageset/Trakt.png
vendored
Normal file
|
After Width: | Height: | Size: 252 KiB |
|
|
@ -25,6 +25,7 @@
|
|||
<string>infuse</string>
|
||||
<string>vlc</string>
|
||||
<string>nplayer-https</string>
|
||||
<string>senplayer</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
|
|
|
|||
95
Sora/Utils/ContinueWatching/DownloadManager.swift
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// DownloadManager.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 29/04/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import AVFoundation
|
||||
|
||||
class DownloadManager: NSObject, ObservableObject {
|
||||
@Published var activeDownloads: [(URL, Double)] = []
|
||||
@Published var localPlaybackURL: URL?
|
||||
|
||||
private var assetDownloadURLSession: AVAssetDownloadURLSession!
|
||||
private var activeDownloadTasks: [URLSessionTask: URL] = [:]
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
initializeDownloadSession()
|
||||
loadLocalContent()
|
||||
}
|
||||
|
||||
private func initializeDownloadSession() {
|
||||
let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader")
|
||||
assetDownloadURLSession = AVAssetDownloadURLSession(
|
||||
configuration: configuration,
|
||||
assetDownloadDelegate: self,
|
||||
delegateQueue: .main
|
||||
)
|
||||
}
|
||||
|
||||
func downloadAsset(from url: URL) {
|
||||
let asset = AVURLAsset(url: url)
|
||||
let task = assetDownloadURLSession.makeAssetDownloadTask(
|
||||
asset: asset,
|
||||
assetTitle: "Offline Video",
|
||||
assetArtworkData: nil,
|
||||
options: nil
|
||||
)
|
||||
|
||||
task?.resume()
|
||||
activeDownloadTasks[task!] = url
|
||||
}
|
||||
|
||||
private func loadLocalContent() {
|
||||
guard let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
||||
|
||||
do {
|
||||
let contents = try FileManager.default.contentsOfDirectory(
|
||||
at: documents,
|
||||
includingPropertiesForKeys: nil,
|
||||
options: .skipsHiddenFiles
|
||||
)
|
||||
|
||||
if let localURL = contents.first(where: { $0.pathExtension == "movpkg" }) {
|
||||
localPlaybackURL = localURL
|
||||
}
|
||||
} catch {
|
||||
print("Error loading local content: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DownloadManager: AVAssetDownloadDelegate {
|
||||
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
activeDownloadTasks.removeValue(forKey: assetDownloadTask)
|
||||
localPlaybackURL = location
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
guard let error = error else { return }
|
||||
print("Download error: \(error.localizedDescription)")
|
||||
activeDownloadTasks.removeValue(forKey: task)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession,
|
||||
assetDownloadTask: AVAssetDownloadTask,
|
||||
didLoad timeRange: CMTimeRange,
|
||||
totalTimeRangesLoaded loadedTimeRanges: [NSValue],
|
||||
timeRangeExpectedToLoad: CMTimeRange) {
|
||||
|
||||
guard let url = activeDownloadTasks[assetDownloadTask] else { return }
|
||||
let progress = loadedTimeRanges
|
||||
.map { $0.timeRangeValue.duration.seconds / timeRangeExpectedToLoad.duration.seconds }
|
||||
.reduce(0, +)
|
||||
|
||||
if let index = activeDownloads.firstIndex(where: { $0.0 == url }) {
|
||||
activeDownloads[index].1 = progress
|
||||
} else {
|
||||
activeDownloads.append((url, progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@
|
|||
import UIKit
|
||||
|
||||
extension UIApplication {
|
||||
|
||||
func dismissKeyboard(_ force: Bool) {
|
||||
if #unavailable(iOS 15) {
|
||||
windows.first?.endEditing(force)
|
||||
|
|
@ -17,5 +16,26 @@ extension UIApplication {
|
|||
windowScene.windows.first?.endEditing(force)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Decodable where Self: UIColor {
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let components = try container.decode([CGFloat].self)
|
||||
self = Self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
|
||||
}
|
||||
}
|
||||
|
||||
extension Encodable where Self: UIColor {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var r, g, b, a: CGFloat
|
||||
(r, g, b, a) = (0, 0, 0, 0)
|
||||
var container = encoder.singleValueContainer()
|
||||
self.getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
try container.encode([r,g,b,a])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIColor: Codable { }
|
||||
|
|
|
|||
|
|
@ -12,3 +12,14 @@ extension View {
|
|||
self.modifier(ShimmeringEffect())
|
||||
}
|
||||
}
|
||||
|
||||
struct SeparatorAlignmentModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
content
|
||||
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
var currentMarqueeConstraints: [NSLayoutConstraint] = []
|
||||
private var currentMenuButtonTrailing: NSLayoutConstraint!
|
||||
|
||||
var subtitleForegroundColor: String = "white"
|
||||
var subtitleForegroundColor: UIColor = .white
|
||||
var subtitleBackgroundEnabled: Bool = true
|
||||
var subtitleFontSize: Double = 20.0
|
||||
var subtitleShadowRadius: Double = 1.0
|
||||
|
|
@ -1336,7 +1336,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
func updateSubtitleLabelAppearance() {
|
||||
for subtitleLabel in subtitleLabels {
|
||||
subtitleLabel.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize))
|
||||
subtitleLabel.textColor = subtitleUIColor()
|
||||
subtitleLabel.textColor = subtitleForegroundColor
|
||||
subtitleLabel.backgroundColor = subtitleBackgroundEnabled
|
||||
? UIColor.black.withAlphaComponent(0.6)
|
||||
: .clear
|
||||
|
|
@ -1932,32 +1932,32 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
|
||||
let foregroundActions = [
|
||||
UIAction(title: "White") { _ in
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = "white" }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = .white }
|
||||
self.loadSubtitleSettings()
|
||||
self.updateSubtitleLabelAppearance()
|
||||
},
|
||||
UIAction(title: "Yellow") { _ in
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = "yellow" }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = .yellow }
|
||||
self.loadSubtitleSettings()
|
||||
self.updateSubtitleLabelAppearance()
|
||||
},
|
||||
UIAction(title: "Green") { _ in
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = "green" }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = .green }
|
||||
self.loadSubtitleSettings()
|
||||
self.updateSubtitleLabelAppearance()
|
||||
},
|
||||
UIAction(title: "Blue") { _ in
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = "blue" }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = .blue }
|
||||
self.loadSubtitleSettings()
|
||||
self.updateSubtitleLabelAppearance()
|
||||
},
|
||||
UIAction(title: "Red") { _ in
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = "red" }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = .red }
|
||||
self.loadSubtitleSettings()
|
||||
self.updateSubtitleLabelAppearance()
|
||||
},
|
||||
UIAction(title: "Purple") { _ in
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = "purple" }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.foregroundColor = .purple }
|
||||
self.loadSubtitleSettings()
|
||||
self.updateSubtitleLabelAppearance()
|
||||
}
|
||||
|
|
@ -2066,17 +2066,16 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
self.presentCustomDelayAlert()
|
||||
}
|
||||
]
|
||||
|
||||
let resetDelayAction = UIAction(title: "Reset Timing") { [weak self] _ in
|
||||
|
||||
let resetDelayAction = UIAction(title: "Reset Delay") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = 0.0 }
|
||||
self.subtitleDelay = 0.0
|
||||
self.loadSubtitleSettings()
|
||||
DropManager.shared.showDrop(title: "Subtitle Timing Reset", subtitle: "", duration: 0.5, icon: UIImage(systemName: "clock.arrow.circlepath"))
|
||||
}
|
||||
|
||||
let delayMenu = UIMenu(title: "Subtitle Timing", children: delayActions + [resetDelayAction])
|
||||
|
||||
|
||||
let delayMenu = UIMenu(title: "Subtitle Delay", children: delayActions + [resetDelayAction])
|
||||
|
||||
let subtitleOptionsMenu = UIMenu(title: "Subtitle Options", children: [
|
||||
subtitlesToggleAction, colorMenu, fontSizeMenu, shadowMenu, backgroundMenu, paddingMenu, delayMenu
|
||||
])
|
||||
|
|
@ -2296,18 +2295,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
.shadow(color: Color.black.opacity(0.6), radius: 4, x: 0, y: 2)
|
||||
}
|
||||
}
|
||||
|
||||
func subtitleUIColor() -> UIColor {
|
||||
switch subtitleForegroundColor {
|
||||
case "white": return .white
|
||||
case "yellow": return .yellow
|
||||
case "green": return .green
|
||||
case "purple": return .purple
|
||||
case "blue": return .blue
|
||||
case "red": return .red
|
||||
default: return .white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// yes? Like the plural of the famous american rapper ye? -IBHRAD
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import UIKit
|
||||
|
||||
struct SubtitleSettings: Codable {
|
||||
var foregroundColor: String = "white"
|
||||
var foregroundColor: UIColor = .white
|
||||
var fontSize: Double = 20.0
|
||||
var shadowRadius: Double = 1.0
|
||||
var backgroundEnabled: Bool = true
|
||||
|
|
|
|||
|
|
@ -2,82 +2,46 @@
|
|||
// DownloadView.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 12/03/25.
|
||||
// Created by Francesco on 29/04/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadItem: Identifiable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
let episode: Int
|
||||
let type: String
|
||||
var progress: Double
|
||||
var status: String
|
||||
}
|
||||
|
||||
class DownloadViewModel: ObservableObject {
|
||||
@Published var downloads: [DownloadItem] = []
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateStatus(_:)), name: .DownloadManagerStatusUpdate, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateStatus(_ notification: Notification) {
|
||||
guard let info = notification.userInfo,
|
||||
let title = info["title"] as? String,
|
||||
let episode = info["episode"] as? Int,
|
||||
let type = info["type"] as? String,
|
||||
let status = info["status"] as? String,
|
||||
let progress = info["progress"] as? Double else { return }
|
||||
|
||||
if let index = downloads.firstIndex(where: { $0.title == title && $0.episode == episode }) {
|
||||
downloads[index] = DownloadItem(title: title, episode: episode, type: type, progress: progress, status: status)
|
||||
} else {
|
||||
let newDownload = DownloadItem(title: title, episode: episode, type: type, progress: progress, status: status)
|
||||
downloads.append(newDownload)
|
||||
}
|
||||
}
|
||||
}
|
||||
import AVKit
|
||||
|
||||
struct DownloadView: View {
|
||||
@StateObject var viewModel = DownloadViewModel()
|
||||
|
||||
@StateObject private var viewModel = DownloadManager()
|
||||
@State private var hlsURL = "https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8"
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List(viewModel.downloads) { download in
|
||||
HStack(spacing: 16) {
|
||||
Image(systemName: iconName(for: download))
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("\(download.title) - Episode \(download.episode)")
|
||||
.font(.headline)
|
||||
|
||||
ProgressView(value: download.progress)
|
||||
.progressViewStyle(LinearProgressViewStyle(tint: .accentColor))
|
||||
.frame(height: 8)
|
||||
|
||||
Text(download.status)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
TextField("Enter HLS URL", text: $hlsURL)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.padding()
|
||||
|
||||
Button("Download Stream") {
|
||||
viewModel.downloadAsset(from: URL(string: hlsURL)!)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding()
|
||||
|
||||
List(viewModel.activeDownloads, id: \.0) { (url, progress) in
|
||||
VStack(alignment: .leading) {
|
||||
Text(url.absoluteString)
|
||||
ProgressView(value: progress)
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink("Play Offline Content") {
|
||||
if let url = viewModel.localPlaybackURL {
|
||||
VideoPlayer(player: AVPlayer(url: url))
|
||||
} else {
|
||||
Text("No offline content available")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("Downloads")
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
func iconName(for download: DownloadItem) -> String {
|
||||
if download.type == "hls" {
|
||||
return download.status.lowercased().contains("converting") ? "arrow.triangle.2.circlepath.circle.fill" : "checkmark.circle.fill"
|
||||
} else {
|
||||
return download.progress >= 1.0 ? "checkmark.circle.fill" : "arrow.down.circle.fill"
|
||||
.navigationTitle("HLS Downloader")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -860,6 +860,8 @@ struct MediaInfoView: View {
|
|||
scheme = "outplayer://\(url)"
|
||||
case "nPlayer":
|
||||
scheme = "nplayer-\(url)"
|
||||
case "SenPlayer":
|
||||
scheme = "SenPlayer://x-callback-url/play?url=\(url)"
|
||||
case "Default":
|
||||
let videoPlayerViewController = VideoPlayerViewController(
|
||||
module: module,
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ struct RootView: View {
|
|||
.tabItem {
|
||||
Label("Explore", systemImage: "star")
|
||||
}
|
||||
LibraryView()
|
||||
.tabItem {
|
||||
Label("Library", systemImage: "books.vertical")
|
||||
}
|
||||
SearchView()
|
||||
.tabItem {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
}
|
||||
LibraryView()
|
||||
.tabItem {
|
||||
Label("Library", systemImage: "books.vertical")
|
||||
}
|
||||
SettingsView()
|
||||
.tabItem {
|
||||
Label("Settings", systemImage: "gear")
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ struct SettingsViewPlayer: View {
|
|||
@AppStorage("skip85Visible") private var skip85Visible: Bool = true
|
||||
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
|
||||
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
|
||||
|
||||
private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"]
|
||||
|
||||
|
||||
private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "Sora"]
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("Media Player"), footer: Text("Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.")) {
|
||||
|
|
@ -112,34 +112,29 @@ struct SettingsViewPlayer: View {
|
|||
}
|
||||
|
||||
struct SubtitleSettingsSection: View {
|
||||
@State private var foregroundColor: String = SubtitleSettingsManager.shared.settings.foregroundColor
|
||||
@State private var foregroundColor: UIColor = SubtitleSettingsManager.shared.settings.foregroundColor
|
||||
@State private var fontSize: Double = SubtitleSettingsManager.shared.settings.fontSize
|
||||
@State private var shadowRadius: Double = SubtitleSettingsManager.shared.settings.shadowRadius
|
||||
@State private var backgroundEnabled: Bool = SubtitleSettingsManager.shared.settings.backgroundEnabled
|
||||
@State private var bottomPadding: CGFloat = SubtitleSettingsManager.shared.settings.bottomPadding
|
||||
@State private var subtitleDelay: Double = SubtitleSettingsManager.shared.settings.subtitleDelay
|
||||
|
||||
private let colors = ["white", "yellow", "green", "blue", "red", "purple"]
|
||||
private let shadowOptions = [0, 1, 3, 6]
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Subtitle Settings")) {
|
||||
HStack {
|
||||
Text("Subtitle Color")
|
||||
Spacer()
|
||||
Menu(foregroundColor) {
|
||||
ForEach(colors, id: \.self) { color in
|
||||
Button(action: {
|
||||
foregroundColor = color
|
||||
SubtitleSettingsManager.shared.update { settings in
|
||||
settings.foregroundColor = color
|
||||
}
|
||||
}) {
|
||||
Text(color.capitalized)
|
||||
}
|
||||
ColorPicker("Subtitle Color", selection: Binding(
|
||||
get: {
|
||||
return Color(foregroundColor)
|
||||
},
|
||||
set: { newColor in
|
||||
let uiColor = UIColor(newColor)
|
||||
foregroundColor = uiColor
|
||||
SubtitleSettingsManager.shared.update { settings in
|
||||
settings.foregroundColor = uiColor
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
HStack {
|
||||
Text("Shadow")
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@
|
|||
|
||||
import SwiftUI
|
||||
import Security
|
||||
import Kingfisher
|
||||
|
||||
struct SettingsViewTrackers: View {
|
||||
@AppStorage("sendPushUpdates") private var isSendPushUpdates = true
|
||||
@AppStorage("sendTraktUpdates") private var isSendTraktUpdates = true
|
||||
|
||||
@State private var anilistStatus: LocalizedStringKey = "You are not logged in"
|
||||
@State private var isAnilistLoggedIn: Bool = false
|
||||
@State private var anilistUsername: String = ""
|
||||
@State private var isAnilistLoading: Bool = false
|
||||
@State private var profileColor: Color = .accentColor
|
||||
|
||||
@AppStorage("sendTraktUpdates") private var isSendTraktUpdates = true
|
||||
@State private var traktStatus: LocalizedStringKey = "You are not logged in"
|
||||
@State private var isTraktLoggedIn: Bool = false
|
||||
@State private var traktUsername: String = ""
|
||||
|
|
@ -27,13 +27,7 @@ struct SettingsViewTrackers: View {
|
|||
Form {
|
||||
Section(header: Text("AniList")) {
|
||||
HStack {
|
||||
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png"))
|
||||
.placeholder {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: 80, height: 80)
|
||||
.shimmering()
|
||||
}
|
||||
Image("AniList")
|
||||
.resizable()
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(Rectangle())
|
||||
|
|
@ -73,16 +67,11 @@ struct SettingsViewTrackers: View {
|
|||
}
|
||||
.font(.body)
|
||||
}
|
||||
.modifier(SeparatorAlignmentModifier())
|
||||
|
||||
Section(header: Text("Trakt")) {
|
||||
HStack {
|
||||
KFImage(URL(string: "https://static-00.iconduck.com/assets.00/trakt-icon-2048x2048-2633ksxg.png"))
|
||||
.placeholder {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: 80, height: 80)
|
||||
.shimmering()
|
||||
}
|
||||
Image("Trakt")
|
||||
.resizable()
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(Rectangle())
|
||||
|
|
@ -116,6 +105,7 @@ struct SettingsViewTrackers: View {
|
|||
}
|
||||
.font(.body)
|
||||
}
|
||||
.modifier(SeparatorAlignmentModifier())
|
||||
|
||||
Section(footer: Text("Sora and cranci1 are not affiliated with AniList nor Trakt in any way.\n\nAlso note that progresses update may not be 100% accurate.")) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,17 @@
|
|||
120D3C712DBA40A40093D596 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
|
||||
126C428A2DB9921C006BC27D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
126C428C2DB99627006BC27D /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewData.swift; sourceTree = "<group>"; };
|
||||
13103E8A2D58E028000F0673 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
13103E8D2D58E04A000F0673 /* SkeletonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCell.swift; sourceTree = "<group>"; };
|
||||
131270162DC13A010093AA9C /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
||||
131270182DC13A3C0093AA9C /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = "<group>"; };
|
||||
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewGeneral.swift; sourceTree = "<group>"; };
|
||||
1327FBA62D758CEA00FC6689 /* Analytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = "<group>"; };
|
||||
1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Model.swift"; sourceTree = "<group>"; };
|
||||
132AF1202D99951700A0140B /* JSController-Streams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Streams.swift"; sourceTree = "<group>"; };
|
||||
132AF1222D9995C300A0140B /* JSController-Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Details.swift"; sourceTree = "<group>"; };
|
||||
132AF1242D9995F900A0140B /* JSController-Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Search.swift"; sourceTree = "<group>"; };
|
||||
133D7C6A2D2BE2500075467E /* Sulfur.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sulfur.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
|
@ -69,12 +80,31 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
123FED0D2DC3F8BD001C4704 /* Recovered References */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
131270162DC13A010093AA9C /* DownloadManager.swift */,
|
||||
1327FBA62D758CEA00FC6689 /* Analytics.swift */,
|
||||
132AF1242D9995F900A0140B /* JSController-Search.swift */,
|
||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */,
|
||||
132AF1202D99951700A0140B /* JSController-Streams.swift */,
|
||||
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */,
|
||||
13103E8D2D58E04A000F0673 /* SkeletonCell.swift */,
|
||||
13103E8A2D58E028000F0673 /* View.swift */,
|
||||
1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */,
|
||||
131270182DC13A3C0093AA9C /* DownloadView.swift */,
|
||||
132AF1222D9995C300A0140B /* JSController-Details.swift */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
126C42872DB99209006BC27D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
126C428A2DB9921C006BC27D /* AVFoundation.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
path = "Tracking Services";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
133D7C612D2BE2500075467E = {
|
||||
|
|
@ -86,6 +116,7 @@
|
|||
120764662DB6F6E0003621E9 /* SulfurTV */,
|
||||
126C42872DB99209006BC27D /* Frameworks */,
|
||||
133D7C6B2D2BE2500075467E /* Products */,
|
||||
123FED0D2DC3F8BD001C4704 /* Recovered References */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ struct RootView: View {
|
|||
.tabItem {
|
||||
Label("Explore", systemImage: "star.fill")
|
||||
}
|
||||
LibraryView()
|
||||
.tabItem {
|
||||
Label("Library", systemImage: "books.vertical.fill")
|
||||
}
|
||||
SearchView()
|
||||
.tabItem {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
}
|
||||
LibraryView()
|
||||
.tabItem {
|
||||
Label("Library", systemImage: "books.vertical.fill")
|
||||
}
|
||||
SettingsView()
|
||||
.tabItem {
|
||||
Label("Settings", systemImage: "gear")
|
||||
|
|
|
|||