shooting like im curry or lebron 🏀 (#144)

This commit is contained in:
Seiike 2025-06-01 09:42:20 +02:00 committed by GitHub
parent 991ff443c2
commit ceea5c9206
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 493 additions and 77 deletions

View file

@ -60,6 +60,20 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
return UserDefaults.standard.bool(forKey: "doubleTapSeekEnabled")
}
private var isPipAutoEnabled: Bool {
UserDefaults.standard.bool(forKey: "pipAutoEnabled")
}
private var isPipButtonVisible: Bool {
if UserDefaults.standard.object(forKey: "pipButtonVisible") == nil {
return true
}
return UserDefaults.standard.bool(forKey: "pipButtonVisible")
}
private var pipController: AVPictureInPictureController?
private var pipButton: UIButton!
var portraitButtonVisibleConstraints: [NSLayoutConstraint] = []
var portraitButtonHiddenConstraints: [NSLayoutConstraint] = []
var landscapeButtonVisibleConstraints: [NSLayoutConstraint] = []
@ -259,6 +273,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
setupAudioSession()
updateSkipButtonsVisibility()
setupHoldSpeedIndicator()
setupPipIfSupported()
view.bringSubviewToFront(subtitleStackView)
@ -1189,6 +1204,53 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
private func setupPipIfSupported() {
guard AVPictureInPictureController.isPictureInPictureSupported() else {
return
}
let pipPlayerLayer = AVPlayerLayer(player: playerViewController.player)
pipPlayerLayer.frame = playerViewController.view.layer.bounds
pipPlayerLayer.videoGravity = .resizeAspect
playerViewController.view.layer.insertSublayer(pipPlayerLayer, at: 0)
pipController = AVPictureInPictureController(playerLayer: pipPlayerLayer)
pipController?.delegate = self
let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)
let Image = UIImage(systemName: "pip", withConfiguration: config)
pipButton = UIButton(type: .system)
pipButton.setImage(Image, for: .normal)
pipButton.tintColor = .white
pipButton.addTarget(self, action: #selector(pipButtonTapped(_:)), for: .touchUpInside)
pipButton.layer.shadowColor = UIColor.black.cgColor
pipButton.layer.shadowOffset = CGSize(width: 0, height: 2)
pipButton.layer.shadowOpacity = 0.6
pipButton.layer.shadowRadius = 4
pipButton.layer.masksToBounds = false
controlsContainerView.addSubview(pipButton)
pipButton.translatesAutoresizingMaskIntoConstraints = false
// NEW: pin pipButton to the left of lockButton:
NSLayoutConstraint.activate([
pipButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor),
pipButton.trailingAnchor.constraint(equalTo: dimButton.leadingAnchor, constant: -8),
pipButton.widthAnchor.constraint(equalToConstant: 44),
pipButton.heightAnchor.constraint(equalToConstant: 44)
])
pipButton.isHidden = !isPipButtonVisible
NotificationCenter.default.addObserver(
self,
selector: #selector(startPipIfNeeded),
name: UIApplication.willResignActiveNotification,
object: nil
)
}
func setupMenuButton() {
let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .bold)
let image = UIImage(systemName: "text.bubble", withConfiguration: config)
@ -1645,6 +1707,24 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc private func pipButtonTapped(_ sender: UIButton) {
guard let pip = pipController else { return }
if pip.isPictureInPictureActive {
pip.stopPictureInPicture()
} else {
pip.startPictureInPicture()
}
}
@objc private func startPipIfNeeded() {
guard isPipAutoEnabled,
let pip = pipController,
!pip.isPictureInPictureActive else {
return
}
pip.startPictureInPicture()
}
@objc private func lockTapped() {
controlsLocked.toggle()
@ -2494,8 +2574,25 @@ class GradientOverlayButton: UIButton {
}
}
extension CustomMediaPlayerViewController: AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_ pipController: AVPictureInPictureController) {
pipButton.alpha = 0.5
}
func pictureInPictureControllerDidStopPictureInPicture(_ pipController: AVPictureInPictureController) {
pipButton.alpha = 1.0
}
func pictureInPictureController(_ pipController: AVPictureInPictureController,
failedToStartPictureInPictureWithError error: Error) {
Logger.shared.log("PiP failed to start: \(error.localizedDescription)", type: "Error")
}
}
// yes? Like the plural of the famous american rapper ye? -IBHRAD
// low taper fade the meme is massive -cranci
// cranci still doesnt have a job -seiike
// The mind is the source of good and evil, only you yourself can decide which you will bring yourself. -seiike
// guys watch Clannad already - ibro
// May the Divine Providence bestow its infinite mercy upon your soul, and may eternal grace find you beyond the shadows of this mortal realm. - paul, 15/11/2005 - 13/05/2023
// May the Divine Providence bestow its infinite mercy upon your soul, and may eternal grace find you beyond the shadows of this mortal realm. - paul, 15/11/2005 - 13/05/2023
// this dumbass defo used gpt

View file

@ -0,0 +1,213 @@
//
// AnilistMatchPopupView.swift
// Sulfur
//
// Created by seiike on 01/06/2025.
//
import SwiftUI
import Kingfisher
struct AnilistMatchPopupView: View {
let seriesTitle: String
let onSelect: (Int) -> Void
@State private var results: [[String: Any]] = []
@State private var isLoading = true
@AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system
@Environment(\.colorScheme) private var colorScheme
private var isLightMode: Bool {
selectedAppearance == .light
|| (selectedAppearance == .system && colorScheme == .light)
}
@State private var manualIDText: String = ""
@State private var showingManualIDAlert = false
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 4) {
// (Optional) A hidden header; can be omitted if empty
Text("".uppercased())
.font(.footnote)
.foregroundStyle(.gray)
.padding(.horizontal, 10)
VStack(spacing: 0) {
if isLoading {
ProgressView()
.frame(maxWidth: .infinity)
.padding()
} else if results.isEmpty {
Text("No matches found")
.font(.subheadline)
.foregroundStyle(.gray)
.frame(maxWidth: .infinity)
.padding()
} else {
LazyVStack(spacing: 15) {
ForEach(results.indices, id: \.self) { index in
let result = results[index]
Button(action: {
if let id = result["id"] as? Int {
onSelect(id)
}
}) {
HStack(spacing: 12) {
if let cover = result["cover"] as? String,
let url = URL(string: cover) {
KFImage(url)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 50, height: 70)
.cornerRadius(6)
}
VStack(alignment: .leading, spacing: 2) {
Text(result["title"] as? String ?? "Unknown")
.font(.body)
.foregroundStyle(.primary)
if let english = result["title_english"] as? String {
Text(english)
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
}
.padding(11)
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 15)
.fill(.ultraThinMaterial)
)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(
LinearGradient(
stops: [
.init(color: Color.accentColor.opacity(0.25), location: 0),
.init(color: Color.accentColor.opacity(0), location: 1)
],
startPoint: .top,
endPoint: .bottom
),
lineWidth: 0.5
)
)
.clipShape(RoundedRectangle(cornerRadius: 15))
}
.buttonStyle(.plain)
}
}
.padding(.horizontal, 20)
.padding(.top, 16)
}
}
if !results.isEmpty {
Text("Tap a title to override the current match.")
.font(.footnote)
.foregroundStyle(.gray)
.padding(.horizontal, 20)
.padding(.top, 4)
}
}
.padding(.top, 2)
}
.navigationTitle("AniList Match")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
.foregroundColor(isLightMode ? .black : .white)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
manualIDText = ""
showingManualIDAlert = true
}) {
Image(systemName: "number")
.foregroundColor(isLightMode ? .black : .white)
}
}
}
.alert("Set Custom AniList ID", isPresented: $showingManualIDAlert, actions: {
TextField("AniList ID", text: $manualIDText)
.keyboardType(.numberPad)
Button("Cancel", role: .cancel) { }
Button("Save", action: {
if let idInt = Int(manualIDText.trimmingCharacters(in: .whitespaces)) {
onSelect(idInt)
dismiss()
}
})
}, message: {
Text("Enter the AniList ID for this media")
})
}
.onAppear(perform: fetchMatches)
}
private func fetchMatches() {
let query = """
query {
Page(page: 1, perPage: 6) {
media(search: "\(seriesTitle)", type: ANIME) {
id
title {
romaji
english
}
coverImage {
large
}
}
}
}
"""
guard let url = URL(string: "https://graphql.anilist.co") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: ["query": query])
URLSession.shared.dataTask(with: request) { data, _, _ in
DispatchQueue.main.async {
self.isLoading = false
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataDict = json["data"] as? [String: Any],
let page = dataDict["Page"] as? [String: Any],
let mediaList = page["media"] as? [[String: Any]] else {
return
}
self.results = mediaList.map { media in
let titleInfo = media["title"] as? [String: Any]
let cover = (media["coverImage"] as? [String: Any])?["large"] as? String
return [
"id": media["id"] ?? 0,
"title": titleInfo?["romaji"] ?? "Unknown",
"title_english": titleInfo?["english"],
"cover": cover
]
}
}
}.resume()
}
}

View file

@ -231,6 +231,15 @@ struct EpisodeCell: View {
.onChange(of: progress) { _ in
updateProgress()
}
.onChange(of: itemID) { newID in
// 1) Clear any cached title/image so that the UI shows the loading spinner:
loadedFromCache = false
isLoading = true
retryAttempts = maxRetryAttempts // reset retries if you want
// 2) Call the same logic you already use to pull per-episode info:
fetchEpisodeDetails()
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("downloadProgressChanged"))) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
updateDownloadStatus()

View file

@ -47,6 +47,8 @@ struct MediaInfoView: View {
@State private var isModuleSelectorPresented = false
@State private var isError = false
@State private var isMatchingPresented = false
@State private var matchedTitle: String? = nil
@StateObject private var jsController = JSController.shared
@EnvironmentObject var moduleManager: ModuleManager
@ -150,6 +152,10 @@ struct MediaInfoView: View {
.onAppear {
buttonRefreshTrigger.toggle()
let savedID = UserDefaults.standard.integer(forKey: "custom_anilist_id_\(href)")
if savedID != 0 {
customAniListID = savedID }
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
if !hasFetched {
@ -212,14 +218,14 @@ struct MediaInfoView: View {
.fill(Color.gray.opacity(0.3))
.shimmering()
}
.setProcessor(ImageUpscaler.lanczosProcessor(scale: 3, sharpeningIntensity: 1, sharpeningRadius: 1))
.setProcessor(ImageUpscaler.lanczosProcessor(scale: 3, sharpeningIntensity: 1.5, sharpeningRadius: 0.8))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width, height: 600)
.clipped()
KFImage(URL(string: imageUrl))
.placeholder { EmptyView() }
.setProcessor(ImageUpscaler.lanczosProcessor(scale: 3, sharpeningIntensity: 1, sharpeningRadius: 1))
.setProcessor(ImageUpscaler.lanczosProcessor(scale: 3, sharpeningIntensity: 1.5, sharpeningRadius: 0.8))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width, height: 600)
@ -458,16 +464,22 @@ struct MediaInfoView: View {
@ViewBuilder
private var menuButton: some View {
Menu {
Button(action: {
showCustomIDAlert()
}) {
Label("Set Custom AniList ID", systemImage: "number")
// Show current match (title if available, else ID)
if let id = itemID ?? customAniListID {
let labelText = (matchedTitle?.isEmpty == false ? matchedTitle! : "\(id)")
Text("Matched with: \(labelText)")
.font(.caption)
.foregroundColor(.gray)
.padding(.vertical, 4)
}
if let customID = customAniListID {
Divider()
if let _ = customAniListID {
Button(action: {
customAniListID = nil
itemID = nil
matchedTitle = nil
fetchItemID(byTitle: cleanTitle(title)) { result in
switch result {
case .success(let id):
@ -490,12 +502,30 @@ struct MediaInfoView: View {
Label("Open in AniList", systemImage: "link")
}
}
Button(action: {
isMatchingPresented = true
}) {
Label("Match with AniList", systemImage: "magnifyingglass")
}
Divider()
Button(action: {
Logger.shared.log("Debug Info:\nTitle: \(title)\nHref: \(href)\nModule: \(module.metadata.sourceName)\nAniList ID: \(itemID ?? -1)\nCustom ID: \(customAniListID ?? -1)", type: "Debug")
DropManager.shared.showDrop(title: "Debug Info Logged", subtitle: "", duration: 1.0, icon: UIImage(systemName: "terminal"))
Logger.shared.log("""
Debug Info:
Title: \(title)
Href: \(href)
Module: \(module.metadata.sourceName)
AniList ID: \(itemID ?? -1)
Custom ID: \(customAniListID ?? -1)
Matched Title: \(matchedTitle ?? "")
""", type: "Debug")
DropManager.shared.showDrop(
title: "Debug Info Logged",
subtitle: "",
duration: 1.0,
icon: UIImage(systemName: "terminal")
)
}) {
Label("Log Debug Info", systemImage: "terminal")
}
@ -509,6 +539,16 @@ struct MediaInfoView: View {
.clipShape(Circle())
.circularGradientOutline()
}
.sheet(isPresented: $isMatchingPresented) {
AnilistMatchPopupView(seriesTitle: title) { selectedID in
// 1) Assign the new AniList ID:
self.customAniListID = selectedID
self.itemID = selectedID
UserDefaults.standard.set(selectedID, forKey: "custom_anilist_id_\(href)")
isMatchingPresented = false
}
}
}
@ViewBuilder
@ -638,41 +678,43 @@ struct MediaInfoView: View {
private var seasonsEpisodeList: some View {
let seasons = groupedEpisodes()
if !seasons.isEmpty, selectedSeason < seasons.count {
ForEach(seasons[selectedSeason]) { ep in
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
EpisodeCell(
episodeIndex: selectedSeason,
episode: ep.href,
episodeID: ep.number - 1,
progress: progress,
itemID: itemID ?? 0,
totalEpisodes: episodeLinks.count,
defaultBannerImage: defaultBannerImageValue,
module: module,
parentTitle: title,
showPosterURL: imageUrl,
isMultiSelectMode: isMultiSelectMode,
isSelected: selectedEpisodes.contains(ep.number),
onSelectionChanged: { isSelected in
if isSelected {
selectedEpisodes.insert(ep.number)
} else {
selectedEpisodes.remove(ep.number)
LazyVStack(spacing: 15) {
ForEach(seasons[selectedSeason]) { ep in
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
EpisodeCell(
episodeIndex: selectedSeason,
episode: ep.href,
episodeID: ep.number - 1,
progress: progress,
itemID: itemID ?? 0,
totalEpisodes: episodeLinks.count,
defaultBannerImage: defaultBannerImageValue,
module: module,
parentTitle: title,
showPosterURL: imageUrl,
isMultiSelectMode: isMultiSelectMode,
isSelected: selectedEpisodes.contains(ep.number),
onSelectionChanged: { isSelected in
if isSelected {
selectedEpisodes.insert(ep.number)
} else {
selectedEpisodes.remove(ep.number)
}
},
onTap: { imageUrl in
episodeTapAction(ep: ep, imageUrl: imageUrl)
},
onMarkAllPrevious: {
markAllPreviousEpisodesAsWatched(ep: ep, inSeason: true)
}
},
onTap: { imageUrl in
episodeTapAction(ep: ep, imageUrl: imageUrl)
},
onMarkAllPrevious: {
markAllPreviousEpisodesAsWatched(ep: ep, inSeason: true)
}
)
)
.disabled(isFetchingEpisode)
}
}
} else {
Text("No episodes available")
@ -721,43 +763,86 @@ struct MediaInfoView: View {
@ViewBuilder
private var flatEpisodeList: some View {
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
let ep = episodeLinks[i]
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
EpisodeCell(
episodeIndex: i,
episode: ep.href,
episodeID: ep.number - 1,
progress: progress,
itemID: itemID ?? 0,
totalEpisodes: episodeLinks.count,
defaultBannerImage: getBannerImageBasedOnAppearance(),
module: module,
parentTitle: title,
showPosterURL: imageUrl,
isMultiSelectMode: isMultiSelectMode,
isSelected: selectedEpisodes.contains(ep.number),
onSelectionChanged: { isSelected in
if isSelected {
selectedEpisodes.insert(ep.number)
} else {
selectedEpisodes.remove(ep.number)
LazyVStack(spacing: 15) {
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
let ep = episodeLinks[i]
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
EpisodeCell(
episodeIndex: i,
episode: ep.href,
episodeID: ep.number - 1,
progress: progress,
itemID: itemID ?? 0,
totalEpisodes: episodeLinks.count,
defaultBannerImage: defaultBannerImageValue,
module: module,
parentTitle: title,
showPosterURL: imageUrl,
isMultiSelectMode: isMultiSelectMode,
isSelected: selectedEpisodes.contains(ep.number),
onSelectionChanged: { isSelected in
if isSelected {
selectedEpisodes.insert(ep.number)
} else {
selectedEpisodes.remove(ep.number)
}
},
onTap: { imageUrl in
episodeTapAction(ep: ep, imageUrl: imageUrl)
},
onMarkAllPrevious: {
markAllPreviousEpisodesInFlatList(ep: ep, index: i)
}
},
onTap: { imageUrl in
episodeTapAction(ep: ep, imageUrl: imageUrl)
},
onMarkAllPrevious: {
markAllPreviousEpisodesInFlatList(ep: ep, index: i)
}
)
)
.disabled(isFetchingEpisode)
}
}
}
private func fetchAniListTitle(id: Int) {
let query = """
query ($id: Int) {
Media(id: $id, type: ANIME) {
title {
english
romaji
}
}
}
"""
let variables: [String: Any] = ["id": id]
guard let url = URL(string: "https://graphql.anilist.co") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: ["query": query, "variables": variables])
URLSession.shared.dataTask(with: request) { data, _, _ in
guard
let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataDict = json["data"] as? [String: Any],
let media = dataDict["Media"] as? [String: Any],
let titleDict = media["title"] as? [String: Any]
else { return }
let english = titleDict["english"] as? String
let romaji = titleDict["romaji"] as? String
let finalTitle = (english?.isEmpty == false ? english! : (romaji ?? "Unknown"))
DispatchQueue.main.async {
matchedTitle = finalTitle
}
}.resume()
}
private func markAllPreviousEpisodesInFlatList(ep: EpisodeLink, index: Int) {
let userDefaults = UserDefaults.standard
var updates = [String: Double]()

View file

@ -203,6 +203,7 @@ struct SettingsViewPlayer: View {
@AppStorage("skip85Visible") private var skip85Visible: Bool = true
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
@AppStorage("pipButtonVisible") private var pipButtonVisible: Bool = true
private let mediaPlayers = ["Default", "Sora", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "IINA"]
private let inAppPlayers = ["Default", "Sora"]
@ -235,6 +236,13 @@ struct SettingsViewPlayer: View {
isOn: $holdForPauseEnabled,
showDivider: false
)
SettingsToggleRow(
icon: "pip",
title: "Show PiP Button",
isOn: $pipButtonVisible,
showDivider: false
)
}
SettingsSection(title: "Speed Settings") {

View file

@ -81,6 +81,7 @@
13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */; };
13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; };
1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */; };
1E47859B2DEBC5960095BF2F /* AnilistMatchPopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E47859A2DEBC5960095BF2F /* AnilistMatchPopupView.swift */; };
1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; };
1EA64DCD2DE5030100AC14BC /* ImageUpscaler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA64DCC2DE5030100AC14BC /* ImageUpscaler.swift */; };
1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; };
@ -178,6 +179,7 @@
13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = "<group>"; };
1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeSlider.swift; sourceTree = "<group>"; };
1E47859A2DEBC5960095BF2F /* AnilistMatchPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnilistMatchPopupView.swift; sourceTree = "<group>"; };
1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = "<group>"; };
1EA64DCC2DE5030100AC14BC /* ImageUpscaler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUpscaler.swift; sourceTree = "<group>"; };
1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = "<group>"; };
@ -361,6 +363,7 @@
133D7C7F2D2BE2630075467E /* MediaInfoView */ = {
isa = PBXGroup;
children = (
1E47859A2DEBC5960095BF2F /* AnilistMatchPopupView.swift */,
138AA1B52D2D66EC0021F9DF /* EpisodeCell */,
133D7C802D2BE2630075467E /* MediaInfoView.swift */,
);
@ -730,6 +733,7 @@
0402DA172DE7B7B8003BB42C /* SearchViewComponents.swift in Sources */,
1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */,
0402DA0E2DE7AA01003BB42C /* TabBarController.swift in Sources */,
1E47859B2DEBC5960095BF2F /* AnilistMatchPopupView.swift in Sources */,
13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */,
133D7C932D2BE2640075467E /* Modules.swift in Sources */,
0457C5972DE7712A000AFBD9 /* DeviceScaleModifier.swift in Sources */,