diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index a91fc3f..a22cdb9 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -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 \ No newline at end of file +// 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 diff --git a/Sora/Views/MediaInfoView/AnilistMatchPopupView.swift b/Sora/Views/MediaInfoView/AnilistMatchPopupView.swift new file mode 100644 index 0000000..57f72ae --- /dev/null +++ b/Sora/Views/MediaInfoView/AnilistMatchPopupView.swift @@ -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() + } +} diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index 9a24e50..4d668de 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -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() diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 0dcd7c4..3fbee2e 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -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]() diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 25a9526..528b952 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -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") { diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index c7dca74..c099e7d 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -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 = ""; }; 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; 1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeSlider.swift; sourceTree = ""; }; + 1E47859A2DEBC5960095BF2F /* AnilistMatchPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnilistMatchPopupView.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EA64DCC2DE5030100AC14BC /* ImageUpscaler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUpscaler.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; @@ -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 */,