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
This commit is contained in:
Seiike 2025-04-25 21:08:41 +02:00 committed by GitHub
parent 05e23a1553
commit 8dbc7e6591
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 223 additions and 36 deletions

View file

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

View file

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

View file

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

View file

@ -11,12 +11,14 @@ import Kingfisher
struct SettingsViewModule: View {
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@EnvironmentObject var moduleManager: ModuleManager
@AppStorage("didReceiveDefaultPageLink") private var didReceiveDefaultPageLink: Bool = false
@State private var errorMessage: String?
@State private var isLoading = false
@State private var isRefreshing = false
@State private var moduleUrl: String = ""
@State private var refreshTask: Task<Void, Never>?
@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()

View file

@ -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 = "<group>"; };
1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = "<group>"; };
1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = "<group>"; };
1EF5C3A82DB988D70032BF07 /* CommunityLib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityLib.swift; sourceTree = "<group>"; };
73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = "<group>"; };
/* 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 */,