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

This commit is contained in:
Dominic Drees 2025-05-01 21:24:46 +02:00
commit 0022451add
42 changed files with 303 additions and 139 deletions

View file

@ -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" : {

View file

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View file

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View file

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View file

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View file

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 248 KiB

View file

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 248 KiB

View file

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View file

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View file

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View file

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View file

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

View file

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 179 KiB

View file

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

View file

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "AniList.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "original"
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Trakt.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "original"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

View file

@ -25,6 +25,7 @@
<string>infuse</string>
<string>vlc</string>
<string>nplayer-https</string>
<string>senplayer</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>

View 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))
}
}
}

View file

@ -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 { }

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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")
}
}
}

View file

@ -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,

View file

@ -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")

View file

@ -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")

View file

@ -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.")) {}
}

View file

@ -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>";
};

View file

@ -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")