From b5a7b43b52d806fe43675a8883fc85ee4c80071c Mon Sep 17 00:00:00 2001 From: cranci <100066266+cranci1@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:12:19 +0200 Subject: [PATCH] Build x2 (#115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed plist * made Anilist push updaes correctly * test episode order * fixed display order * Revert "fixed display order" This reverts commit fd3591c66632faae7946847e271577f4f9d7576c. * Revert "test episode order" This reverts commit 1637383a19680bc803913ffac2ee9e9ca7cf626c. * there is now 😏 (#105) * the great logic of not sending the user back when unbookmark * fixed subtitle view being behind the skip 85s button * bug fix progress bar no longer flashes back to the previous position it was in before scrubbing * moved dim button * skip intro/outro bug fix using invisble overlay no longer lets the skip buttons be visible * bug fix segment marker being outside of the progress bar * beautiful ahh skip buttons https://discord.com/channels/1293430817841741899/1318240587886891029/1364701327120269476 * community library??? * now it will 😼 * Update CommunityLib.swift * no comments + restored older code * cuts off at the tab bar * its perfect now just need to add some drops * eh * donezo * test --------- Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com> --- Sora/SoraApp.swift | 86 +++++++++++---- Sora/Utils/Modules/CommunityLib.swift | 104 ++++++++++++++++++ .../Modules/ModuleAdditionSettingsView.swift | 1 + Sora/Views/MediaInfoView/MediaInfoView.swift | 6 +- .../SettingsSubViews/SettingsViewModule.swift | 64 ++++++++--- Sulfur.xcodeproj/project.pbxproj | 4 + 6 files changed, 228 insertions(+), 37 deletions(-) create mode 100644 Sora/Utils/Modules/CommunityLib.swift diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index 4f34d9a..8729080 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -12,7 +12,7 @@ struct SoraApp: App { @StateObject private var settings = Settings() @StateObject private var moduleManager = ModuleManager() @StateObject private var librarykManager = LibraryManager() - + init() { TraktToken.checkAuthenticationStatus { isAuthenticated in if isAuthenticated { @@ -22,7 +22,7 @@ struct SoraApp: App { } } } - + var body: some Scene { WindowGroup { ContentView() @@ -47,33 +47,71 @@ struct SoraApp: App { } } } - + private func handleURL(_ url: URL) { - guard url.scheme == "sora", - url.host == "module", - let components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let moduleURL = components.queryItems?.first(where: { $0.name == "url" })?.value else { - return - } - - let addModuleView = ModuleAdditionSettingsView(moduleUrl: moduleURL).environmentObject(moduleManager) - let hostingController = UIHostingController(rootView: addModuleView) - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first { - window.rootViewController?.present(hostingController, animated: true) - } else { - Logger.shared.log("Failed to present module addition view: No window scene found", type: "Error") + guard url.scheme == "sora", let host = url.host else { return } + switch host { + case "default_page": + if let comps = URLComponents(url: url, resolvingAgainstBaseURL: true), + let libraryURL = comps.queryItems?.first(where: { $0.name == "url" })?.value { + + UserDefaults.standard.set(libraryURL, forKey: "lastCommunityURL") + UserDefaults.standard.set(true, forKey: "didReceiveDefaultPageLink") + + let communityView = CommunityLibraryView() + .environmentObject(moduleManager) + let hostingController = UIHostingController(rootView: communityView) + DispatchQueue.main.async { + if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = scene.windows.first, + let root = window.rootViewController { + root.present(hostingController, animated: true) { + DropManager.shared.showDrop( + title: "Module Library Added", + subtitle: "You can browse the community library in settings.", + duration: 2, + icon: UIImage(systemName: "books.vertical.circle.fill") + ) + } + } + } + } + + case "module": + guard url.scheme == "sora", + url.host == "module", + let components = URLComponents(url: url, resolvingAgainstBaseURL: true), + let moduleURL = components.queryItems?.first(where: { $0.name == "url" })?.value + else { + return + } + + let addModuleView = ModuleAdditionSettingsView(moduleUrl: moduleURL) + .environmentObject(moduleManager) + let hostingController = UIHostingController(rootView: addModuleView) + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first { + window.rootViewController?.present(hostingController, animated: true) + } else { + Logger.shared.log( + "Failed to present module addition view: No window scene found", + type: "Error" + ) + } + + default: + break } } - + static func handleRedirect(url: URL) { guard let params = url.queryParameters, let code = params["code"] else { - Logger.shared.log("Failed to extract authorization code") - return - } - + Logger.shared.log("Failed to extract authorization code") + return + } + switch url.host { case "anilist": AniListToken.exchangeAuthorizationCodeForToken(code: code) { success in @@ -83,6 +121,7 @@ struct SoraApp: App { Logger.shared.log("AniList token exchange failed", type: "Error") } } + case "trakt": TraktToken.exchangeAuthorizationCodeForToken(code: code) { success in if success { @@ -91,6 +130,7 @@ struct SoraApp: App { Logger.shared.log("Trakt token exchange failed", type: "Error") } } + default: Logger.shared.log("Unknown authentication service", type: "Error") } diff --git a/Sora/Utils/Modules/CommunityLib.swift b/Sora/Utils/Modules/CommunityLib.swift new file mode 100644 index 0000000..079f30a --- /dev/null +++ b/Sora/Utils/Modules/CommunityLib.swift @@ -0,0 +1,104 @@ +// +// CommunityLib.swift +// Sulfur +// +// Created by seiike on 23/04/2025. +// + +import SwiftUI +@preconcurrency import WebKit + +private struct ModuleLink: Identifiable { + let id = UUID() + let url: String +} + +struct CommunityLibraryView: View { + @EnvironmentObject var moduleManager: ModuleManager + + @AppStorage("lastCommunityURL") private var inputURL: String = "" + @State private var webURL: URL? + @State private var errorMessage: String? + @State private var moduleLinkToAdd: ModuleLink? + + var body: some View { + VStack(spacing: 0) { + if let err = errorMessage { + Text(err) + .foregroundColor(.red) + .padding(.horizontal) + } + + WebView(url: webURL) { linkURL in + + if let comps = URLComponents(url: linkURL, resolvingAgainstBaseURL: false), + let m = comps.queryItems?.first(where: { $0.name == "url" })?.value { + moduleLinkToAdd = ModuleLink(url: m) + } + } + .ignoresSafeArea(edges: .top) + } + .onAppear(perform: loadURL) + .sheet(item: $moduleLinkToAdd) { link in + ModuleAdditionSettingsView(moduleUrl: link.url) + .environmentObject(moduleManager) + } + } + + private func loadURL() { + var s = inputURL.trimmingCharacters(in: .whitespacesAndNewlines) + if !s.hasPrefix("http://") && !s.hasPrefix("https://") { + s = "https://" + s + } + inputURL = s + if let u = URL(string: s) { + webURL = u + errorMessage = nil + } else { + webURL = nil + errorMessage = "Invalid URL" + } + } +} + +struct WebView: UIViewRepresentable { + let url: URL? + let onCustomScheme: (URL) -> Void + + func makeCoordinator() -> Coordinator { + Coordinator(onCustom: onCustomScheme) + } + + func makeUIView(context: Context) -> WKWebView { + let cfg = WKWebViewConfiguration() + cfg.preferences.javaScriptEnabled = true + let wv = WKWebView(frame: .zero, configuration: cfg) + wv.navigationDelegate = context.coordinator + return wv + } + + func updateUIView(_ uiView: WKWebView, context: Context) { + if let u = url { + uiView.load(URLRequest(url: u)) + } + } + + class Coordinator: NSObject, WKNavigationDelegate { + let onCustom: (URL) -> Void + init(onCustom: @escaping (URL) -> Void) { self.onCustom = onCustom } + + func webView(_ webView: WKWebView, + decidePolicyFor action: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) + { + if let url = action.request.url, + url.scheme == "sora", url.host == "module" + { + onCustom(url) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } + } +} diff --git a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift index 2afb2da..fb49ef0 100644 --- a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift +++ b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift @@ -150,6 +150,7 @@ struct ModuleAdditionSettingsView: View { await MainActor.run { self.errorMessage = "Invalid URL" self.isLoading = false + Logger.shared.log("Failed to open add module ui with url: \(moduleUrl)", type: "Error") } return } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index a967891..2695db4 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -59,6 +59,8 @@ struct MediaInfoView: View { @State private var activeFetchID: UUID? = nil @Environment(\.dismiss) private var dismiss + @State private var orientationChanged: Bool = false + private var isGroupedBySeasons: Bool { return groupedEpisodes().count > 1 } @@ -451,6 +453,9 @@ struct MediaInfoView: View { } selectedRange = 0..? + @State private var showLibrary = false var body: some View { VStack { @@ -28,15 +30,26 @@ struct SettingsViewModule: View { .foregroundColor(.secondary) Text("No Modules") .font(.headline) - Text("Click the plus button to add a module!") - .font(.caption) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity) + + if didReceiveDefaultPageLink { + NavigationLink(destination: CommunityLibraryView() + .environmentObject(moduleManager)) { + Text("Check out some community modules here!") + .font(.caption) + .foregroundColor(.accentColor) + .frame(maxWidth: .infinity) + } + .buttonStyle(PlainButtonStyle()) + } else { + Text("Click the plus button to add a module!") + .font(.caption) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity) + } } .padding() .frame(maxWidth: .infinity) - } - else { + } else { ForEach(moduleManager.modules) { module in HStack { KFImage(URL(string: module.metadata.iconUrl)) @@ -105,13 +118,38 @@ struct SettingsViewModule: View { } } .navigationTitle("Modules") - .navigationBarItems(trailing: Button(action: { - showAddModuleAlert() - }) { - Image(systemName: "plus") - .resizable() - .padding(5) - }) + .navigationBarItems(trailing: + HStack(spacing: 16) { + if didReceiveDefaultPageLink && !moduleManager.modules.isEmpty { + Button(action: { + showLibrary = true + }) { + Image(systemName: "books.vertical.fill") + .resizable() + .frame(width: 20, height: 20) + .padding(5) + } + .accessibilityLabel("Open Community Library") + } + + Button(action: { + showAddModuleAlert() + }) { + Image(systemName: "plus") + .resizable() + .frame(width: 20, height: 20) + .padding(5) + } + .accessibilityLabel("Add Module") + } + ) + .background( + NavigationLink( + destination: CommunityLibraryView() + .environmentObject(moduleManager), + isActive: $showLibrary + ) { EmptyView() } + ) .refreshable { isRefreshing = true refreshTask?.cancel() diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 3d72d16..89079d9 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; + 1EF5C3A92DB988E40032BF07 /* CommunityLib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF5C3A82DB988D70032BF07 /* CommunityLib.swift */; }; 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; }; /* End PBXBuildFile section */ @@ -125,6 +126,7 @@ 1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeSlider.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; + 1EF5C3A82DB988D70032BF07 /* CommunityLib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityLib.swift; sourceTree = ""; }; 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -286,6 +288,7 @@ 133D7C882D2BE2640075467E /* Modules */ = { isa = PBXGroup; children = ( + 1EF5C3A82DB988D70032BF07 /* CommunityLib.swift */, 13D99CF62D4E73C300250A86 /* ModuleAdditionSettingsView.swift */, 139935652D468C450065CEFF /* ModuleManager.swift */, 133D7C892D2BE2640075467E /* Modules.swift */, @@ -526,6 +529,7 @@ 139935662D468C450065CEFF /* ModuleManager.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 132AF1252D9995F900A0140B /* JSController-Search.swift in Sources */, + 1EF5C3A92DB988E40032BF07 /* CommunityLib.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, 1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */, 136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */,